简体   繁体   English

在Windows上停止多线程Python脚本

[英]Stop multithreaded Python script on Windows

I have troubles with a simple multithreaded Python looping program. 我在使用简单的多线程Python循环程序时遇到了麻烦。 It should loop infinitely and stop with Ctrl + C . 它应该无限循环,并按Ctrl + C停止。 Here is an implementation using threading : 这是使用threading的实现:

from threading import Thread, Event
from time import sleep

stop = Event()

def loop():
    while not stop.is_set():
        print("looping")
        sleep(2)

try:
    thread = Thread(target=loop)
    thread.start()
    thread.join()

except KeyboardInterrupt:
    print("stopping")
    stop.set()

This MWE is extracted from a more complex code (obviously, I do not need multithreading to create an infinite loop). 这MWE是从更复杂的代码(显然,我不需要多线程创建一个无限循环)提取。

It works as expected on Linux, but not on Windows: the Ctrl + C event is not intercepted and the loop continues infinitely. 它可以在Linux上正常运行,但不能在Windows上运行: Ctrl + C事件不会被拦截,并且循环将无限期继续。 According to the Python Dev mailing list , the different behaviors are due to the way Ctrl + C is handled by the two OSs. 根据Python Dev邮件列表 ,不同的行为归因于两个操作系统处理Ctrl + C的方式。

So, it appears that one cannot simply rely on Ctrl + C with threading on Windows. 因此,似乎不能简单地依靠Ctrl + C在Windows上进行threading My question is: what are the other ways to stop a multithreaded Python script on this OS with Ctrl + C ? 我的问题是:使用Ctrl + C可以在此OS上停止多线程Python脚本的其他方法是什么?

As explained by Nathaniel J. Smith in the link from your question, at least as of CPython 3.7, Ctrl-C cannot wake your main thread on Windows: 正如Nathaniel J. Smith在问题链接中所解释的那样,至少从CPython 3.7版本开始,Ctrl-C无法唤醒Windows上的主线程:

The end result is that on Windows, control-C almost never works to wake up a blocked Python process, with a few special exceptions where someone did the work to implement this. 最终结果是,在Windows上,control-C几乎永远无法唤醒被阻塞的Python进程,但有一些特殊的例外情况是有人为实现此目的而做的工作。 On Python 2 the only functions that have this implemented are time.sleep() and multiprocessing.Semaphore.acquire; 在Python 2上,唯一实现此功能的功能是time.sleep()和multiprocessing.Semaphore.acquire;。 on Python 3 there are a few more (you can grep the source for _PyOS_SigintEvent to find them), but Thread.join isn't one of them. 在Python 3上还有更多(您可以grep _PyOS_SigintEvent的源以找到它们),但是Thread.join并不是其中之一。

So, what can you do? 所以,你可以做什么?


One option is to just not use Ctrl-C to kill your program, and instead use something that calls, eg, TerminateProcess , such as the builtin taskkill tool, or a Python script using the os module. 一种选择是不使用Ctrl-C杀死程序,而使用诸如TerminateProcess类的调用,例如内置的taskkill工具,或使用os模块的Python脚本。 But you don't want that. 但是你不想要那样。

And obviously, waiting until they come up with a fix in Python 3.8 or 3.9 or never before you can Ctrl-C your program is not acceptable. 而且显然,等到他们提出Python 3.8或3.9中的修复程序,或者等到您无法使用Ctrl-C程序之前,再也不能接受。

So, the only thing you can do is not block the main thread on Thread.join , or anything else non-interruptable. 因此,您唯一可以做的就是不阻塞Thread.join上的主线程或其他任何不可中断的东西。


The quick&dirty solution is to just poll join with a timeout: 快速和肮脏的解决方法就是轮询join一个超时:

while thread.is_alive():
    thread.join(0.2)

Now, your program is briefly interruptable while it's doing the while loop and calling is_alive , before going back to an uninterruptable sleep for another 200ms. 现在,您的程序在执行while循环并调用is_alive时可以短暂中断,然后再返回200ms的不间断睡眠。 Any Ctrl-C that comes in during that 200ms will just wait for you to process it, so that isn't a problem. 在这200ms内出现的任何Ctrl-C都将等待您对其进行处理,因此这不是问题。

Except that 200ms is already long enough to be noticeable and maybe annoying. 除了200毫秒已经足够长,足以引起注意并可能令人讨厌。

And it may be too short as well as too long. 可能太短也太长。 Sure, it's not wasting much CPU to wake up every 200ms and execute a handful of Python bytecodes, but it's not nothing , and it's still getting a timeslice in the scheduler, and that may be enough to, eg, keep a laptop from going into one of its long-term low-power modes. 当然,每200毫秒唤醒一次并执行少量Python字节码并不会浪费太多的CPU,但这并不是没有 ,并且它还在调度程序中增加了时间片,例如,足以阻止笔记本电脑进入它的长期低功耗模式之一。


The clean solution is to find another function to block on. 干净的解决方案是找到另一个要阻止的功能。 As Nathaniel J. Smith says: 正如纳撒尼尔·史密斯(Nathaniel J. Smith)所说:

you can grep the source for _PyOS_SigintEvent to find them 您可以grep _PyOS_SigintEvent的源以找到它们

But there may not be anything that fits very well. 但是可能没有什么合适的。 It's hard to imagine how you'd design your program to block on multiprocessing.Semaphore.acquire in a way that wouldn't be horribly confusing to the reader… 很难想象您将如何设计程序以阻止在multiprocessing.Semaphore.acquire上进行multiprocessing.Semaphore.acquire ,而这不会使读者感到困惑……

In that case, you might want to drag in the Win32 API directly, whether via PyWin32 or ctypes . 在这种情况下,您可能想直接通过PyWin32ctypes拖动Win32 API。 Look at how functions like time.sleep and multiprocessing.Semaphore.acquire manage to be interruptible, block on whatever they're using, and have your thread signal whatever it is you're blocking on at exit. 看一下time.sleepmultiprocessing.Semaphore.acquire类的功能如何可中断,阻塞它们正在使用的内容,以及让线程发出信号,无论您在退出时阻塞什么。


If you're willing to use undocumented internals of CPython, it looks like, at least in 3.7, the hidden _winapi module has a wrapper function around WaitForMultipleObjects that appends the magic _PyOSSigintEvent for you when you're doing a wait-first rather than wait-all. 如果您愿意使用未记录的CPython内部函数 ,则至少在3.7中,看起来隐藏的_winapi模块在WaitForMultipleObjects周围具有包装函数 ,该函数在您执行等待优先而不是等待时为您附加了魔术_PyOSSigintEvent -所有。

One of the things you can pass to WaitForMultipleObjects is a Win32 thread handle, which has the same effect as a join , although I'm not sure if there's an easy way to get the thread handle out of a Python thread. 可以传递给WaitForMultipleObjects的东西之一是Win32线程句柄,它具有与join相同的作用,尽管我不确定是否有一种简单的方法可以将线程句柄从Python线程中取出。

Alternatively, you can manually create some kind of kernel sync object (I don't know the _winapi module very well, and I don't have a Windows system, so you'll probably have to read the source yourself, or at least help it in the interactive interpreter, to see what wrappers it offers), WaitForMultipleObjects on that, and have the thread signal it. 或者,您可以手动创建某种内核同步对象(我不太了解_winapi模块,并且我没有Windows系统,因此您可能必须自己阅读源代码,或者至少要help在交互式解释器中查看它提供的包装器), WaitForMultipleObjects并让线程发出信号。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM