gpt4 book ai didi

python - Python中的多线程诅咒输出

转载 作者:太空宇宙 更新时间:2023-11-03 13:59:09 25 4
gpt4 key购买 nike

我正在尝试在一个长时间运行的函数的进度条下方实现一个简单的微调器(使用改编自 this answer 的代码)。

[########         ] x%
/ Compressing filename

我在脚本的主线程中运行压缩和进度条,在另一个线程中运行微调器,因此它实际上可以在压缩发生时旋转。但是,我对进度条和微调器都使用了 curses,并且都使用了 curses.refresh()

有时终端会随机输出乱码,我不确定为什么。我认为这是由于微调器的多线程特性所致,因为当我禁用微调器时,问题就消失了。

这是微调器的伪代码:

def start(self):
self.busy = True
global stdscr
stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
threading.Thread(target=self.spinner_task).start()

def spinner_task(self):
while self.busy:
stdscr.addstr(1, 0, next(self.spinner_generator))
time.sleep(self.delay)
stdscr.refresh()

这是进度条的伪代码:

progress_bar = "\r[{}] {:.0f}%".format("#" * block + " " * (bar_length - block), round(progress * 100, 0))
progress_file = " {} {}".format(s, filename)
stdscr.clrtoeol()
stdscr.addstr(1, 1, " ")
stdscr.clrtoeol()
stdscr.addstr(0, 0, progress_bar)
stdscr.addstr(1, 1, progress_file)
stdscr.refresh()

并从 main() 中调用,例如:

spinner.start()
for each file:
update_progress_bar
compress(file)
spinner.stop()

为什么输出有时会损坏?是因为单独的线程吗?如果是这样,关于更好的设计方法有什么建议吗?

最佳答案

Python 的 curses 模块所依赖的 curses 库不是线程安全的。

ncurses 有一个 curs_threads功能,它显然自大约十年前的 5.7 以来就已经存在。但它需要改变您执行一些 API 调用的方式,并链接到 -lncursest,它仍然不是微不足道的,而且……几乎没有人使用过它。

据我所知,没有任何标准安装程序或发行包会构建 Python curses 来链接 ncursest——即使发行版包含 ncursest首先,他们通常不会这样做。即使他们这样做了,也没有线程安全函数的绑定(bind),所以您仍然无法安全地访问诸如设置 tabsize 之类的东西。


根据我的(可能已过时,并且可能受平台限制)经验,您仍然可以摆脱困境,但您需要:

  • 显然只有一个线程可以调用诸如 getchgetmouse 之类的东西。
  • 添加全局Lock,然后确保每批更新都以refresh结束,并且整批更新都在Lock 中。
  • 避免围绕 curs_threads 中提到的功能使用 Python 包装器——例如,不要更改 escdelay 或 tabsize。
  • 在启动(退出后)其他线程之前,从主线程启动(并关闭)屏幕。
  • 如果可能的话,请确保您还在主线程中创建了您需要的所有窗口。 (希望你不想要任何动态弹出子窗口或任何东西......)

但是安全的方法是做与 tkinter 相同的事情。或其他不了解线程的 GUI 库。它不完全相同,但想法相似。最简单的版本是:

  • 将主线程的工作移至另一个后台线程。
  • 添加一个 queue.Queue 以便您的后台线程可以请求运行 curses 命令。 (你不需要任何复杂的东西来表示一个“命令”,它只是一个 (func, *args) 元组,因为 Python。)
  • 让主线程循环从队列中弹出命令并调用它们。

如果您的后台线程需要调用返回值的函数,显然您需要稍微复杂一些。您可以查看 multiprocessing.dummy.AsyncResultconcurrent.futures.Future 的工作原理。或者你甚至可以窃取 Future 用于你自己的目的。但您可能不需要任何复杂的东西。

如果你在循环输入,你可能还希望你的主线程这样做(这意味着选择一个“帧速率”并在等待队列和输入之间交替,超时)并分派(dispatch)它,即使您总是分派(dispatch)到同一个线程。

你甚至可以写一个 mtTkinter重现 curses 接口(interface)(甚至是 monkeypatches curses 模块)但用将函数和参数放入队列的调用替换每个函数的样式包装器。但我不确定这是否值得付出努力。

关于python - Python中的多线程诅咒输出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51315269/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com