【问题标题】:Terminating a process breaks python curses终止进程会破坏 python 诅咒
【发布时间】:2023-03-26 07:28:01
【问题描述】:

使用 python 多处理和curses,终止进程似乎会干扰curses显示。
例如,在下面的代码中,为什么终止进程会阻止 curses 显示文本? (按a后按b)
更准确地说,似乎不仅字符串“hello”不再显示,而且整个curses窗口也不再显示。

import curses
from multiprocessing import Process
from time import sleep

def display(stdscr):
    stdscr.clear()
    curses.newwin(0,0)
    stdscr.timeout(500)
    p = None
    while True:
        stdscr.addstr(1, 1, "hello")
        stdscr.refresh()
        key = stdscr.getch()
        if key == ord('a') and not p:
            p = Process(target = hang)
            p.start()
        elif key == ord('b') and p:
            p.terminate()

def hang():
    sleep(100)

if __name__ == '__main__':
    curses.wrapper(display)

我在 GNU/Linux 下运行 python 3.6。

编辑:
我仍然能够使用这个不调用 sleep() 的更精简的版本进行复制。现在只需按“a”即可触发错误。

import curses
from multiprocessing import Process

def display(stdscr):
    stdscr.clear()
    curses.newwin(0,0)
    stdscr.timeout(500)
    p = None
    while True:
        stdscr.addstr(1, 1, "hello")
        stdscr.refresh()
        key = stdscr.getch()
        if key == ord('a') and not p:
            p = Process(target = hang)
            p.start()
            p.terminate()

def hang():
    while True:
        temp = 1 + 1

if __name__ == '__main__':
    curses.wrapper(display)

【问题讨论】:

  • 根据文档,当进程使用锁或信号量时,使用terminate()可能会导致问题:Warning If this method is used when the associated process is using a pipe or queue then the pipe or queue is liable to become corrupted and may become unusable by other process. Similarly, if the process has acquired a lock or semaphore etc. then terminating it is liable to cause other processes to deadlock.我不确定sleep是如何实现的,但这可能是原因. docs.python.org/3/library/…
  • @amuttsch 好主意,但不是这样,请参阅我的编辑。

标签: python python-multiprocessing python-curses


【解决方案1】:

以下代码有效:

import curses
from multiprocessing import Process

p = None
def display(stdscr):
    stdscr.clear()
    curses.newwin(0,0)
    stdscr.timeout(500)
    while True:
        stdscr.addstr(1, 1, "hello")
        stdscr.refresh()
        key = stdscr.getch()
        if key == ord('a') and not p:
            p.start()
            p.terminate()

def hang():
    while True:
        temp = 1 + 1

if __name__ == '__main__':
    p = Process(target = hang)
    curses.wrapper(display)

在使用 curses.wrapper() 初始化 UI 之前,我创建了一个新的 Process。好的,为什么这行得通?为此,我们必须知道Proccess 的工作原理以及调用Process(target = hang) 时的具体作用:

分叉

父进程使用 os.fork() 来分叉 Python 解释器。子进程在开始时实际上与父进程相同。父进程的所有资源都由子进程继承。请注意,安全地分叉多线程进程是有问题的。

仅在 Unix 上可用。 Unix 上的默认值。

现在,它告诉我们什么?当您已经创建了 curses 屏幕时,您在哪里创建了一个新的 Processescurses.wrapper() 是做什么的?

在调用 func 之前,wrapper() 打开 cbreak 模式,关闭 echo,启用终端键盘,如果终端支持颜色,则初始化颜色。退出时(无论是正常退出还是异常退出),它会恢复煮熟模式、打开回显并禁用终端键盘。

好的,我们有一个新创建的子进程,它的资源与其父进程完全相同。当您调用 terminate() 杀死孩子时,它会释放所有资源,包括 curses 包装器。它会恢复以前的终端设置,因此会破坏您的 UI。

要解决此问题,您必须以不同的方式实现您的程序。预先创建一个新进程,使用IPC与你的进程通信,如果你需要多个进程,使用Process Pools,如果你有IO绑定任务,使用ThreadingThread Pools

【讨论】:

  • 谢谢。虽然我仍然不确定为什么释放子进程的资源会弄乱父进程的资源?
  • 除此之外,在我的实际代码中,我确实有一个启动进程的 UI 操作。据我了解,使用池或预先创建进程会限制此操作的使用次数,这在可用性方面存在问题。不依赖包装器并手动进行初始化是可接受的解决方案吗?
  • 它不会弄乱父资源,但由于curses屏幕是通过wrapper初始化的,它会重置终端设置。父母不知道这一点。
  • 你可以试试不使用wrapper。我不知道您的确切代码,因此很难就您的用例的最佳解决方案提供好的建议。你想在子任务中执行什么?请记住:并行编程很难,因此您必须找出解决问题的最佳方法。阅读有关进程和线程的文档,这应该可以帮助您做出决定。
  • 您也可以尝试使用spawn作为启动方法,然后不会将所有资源都复制到子进程中。
【解决方案2】:

也许你是在 Windows 上运行它吗?该平台上多处理模块的记录要求之一是所有顶级代码(在您的情况下为curses.wrapper(display))必须位于if __name__ == '__main__': 块内,以便它不会在您生成的进程中意外执行。

我认为这里发生的情况是您生成的进程正在初始化 curses 本身(这涉及适当地设置控制台),然后在控制台终止时将控制台恢复为正常状态 - 从而撤消原始程序所做的设置。

【讨论】:

  • 您好,感谢您的回答,我编辑了我的问题以添加平台信息和所需的块,在剥离我的实际代码时我忘记了。但是,它对问题没有任何改变;你不能复制它吗?
  • 也许您还可以澄清“阻止诅咒显示文本”的含义。启动或停止该过程后不会显示其他文本,并且开始时显示的“你好”不断刷新,所以我不确定实际发生了什么(而且我无法尝试重现时刻)。
  • 已编辑以增加精度,但它可能应该被看到而不是被描述。打算每 0.5 秒刷新一次,实际发生的情况是个问题。
猜你喜欢
  • 2019-12-14
  • 2015-08-14
  • 2018-07-09
  • 2016-07-07
  • 1970-01-01
  • 2012-04-08
  • 1970-01-01
  • 2014-04-23
  • 2013-11-22
相关资源
最近更新 更多