Using python multiprocessing and curses, it appears that terminating a Process interferes with curses display.
For example, in the following code, why does terminating the process stops curses from displaying the text ? (pressing b after pressing a)
More precisely, it seems that not only the string "hello" is not displayed anymore but also the whole 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)
I'm running python 3.6 under GNU/Linux.
Edit :
I'm still able to reproduce with this more stripped down version which doesn't call sleep(). Now just pressing "a" triggers the bug.
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)
The following code works:
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)
I created a new Process
before initializing the UI using curses.wrapper()
. Okay, why does this work? For this we must know how Proccess works and what exactly it does when you call 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.
Now, what does it tell us? You where creating a new Processes
when you already created a curses
screen. What does curses.wrapper() do?
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.
Okay, we have a newly created child process that has the exact same resources as its parent. When you call terminate()
to kill the child, it frees all resources, including the curses wrapper. It restores the previous terminal settings and therefore breaks your UI.
To fix this you have to implement your program differently. Create a new process beforehand, use IPC to communicate with your process, use Process Pools if you need multiple processes, Threading or Thread Pools if you have IO bound tasks.
Are you running this on Windows, perhaps? One of the documented requirements of the multiprocessing module on that platform is that all top-level code (the curses.wrapper(display)
in your case) MUST be inside an if __name__ == '__main__':
block, so that it isn't accidentally executed in your spawned process.
I think what's happening here is that your spawned process is initializing curses itself (which involves setting up the console appropriately), and then reverts the console to normal when it terminates - thus undoing the setup done by the original program.
来源:https://stackoverflow.com/questions/44677774/terminating-a-process-breaks-python-curses