终止进程会中断 python 诅咒

Terminating a process breaks python curses

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

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)

我 运行 python 3.6 低于 GNU/Linux。

编辑:
我仍然能够使用这个不调用 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)

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

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

以下代码有效:

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):

时它究竟做了什么

fork

The parent process uses os.fork() to fork the Python interpreter. The child process, when it begins, is effectively identical to the parent process. All resources of the parent are inherited by the child process. Note that safely forking a multithreaded process is problematic.

Available on Unix only. The default on Unix.

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

Before calling func, wrapper() turns on cbreak mode, turns off echo, enables the terminal keypad, and initializes colors if the terminal has color support. On exit (whether normally or by exception) it restores cooked mode, turns on echo, and disables the terminal keypad.

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

要解决这个问题,您必须以不同的方式实现您的程序。预先创建一个新进程,如果您有 IO 绑定任务,请使用 IPC to communicate with your process, use Process Pools if you need multiple processes, Threading or Thread Pools