停止Windows上的多线程Python脚本
我在使用简单的多线程Python循环程序时遇到问题。它应该无限循环并使用Ctrl+C停止。下面是一个使用停止Windows上的多线程Python脚本,python,windows,multithreading,python-multithreading,Python,Windows,Multithreading,Python Multithreading,我在使用简单的多线程Python循环程序时遇到问题。它应该无限循环并使用Ctrl+C停止。下面是一个使用线程的实现: 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
线程的实现:
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()
这个MWE是从更复杂的代码中提取出来的(显然,我不需要多线程来创建无限循环)
它在Linux上按预期工作,但在Windows上不工作:Ctrl+C事件不会被截获,循环将无限继续。根据,不同的行为是由于两个操作系统处理Ctrl+C的方式不同
因此,在Windows上,似乎不能简单地依靠Ctrl+C和线程处理。我的问题是:使用Ctrl+C在这个操作系统上停止多线程Python脚本还有什么其他方法?正如Nathaniel J.Smith在您问题的链接中所解释的,至少在CPython 3.7中,Ctrl-C无法唤醒Windows上的主线程:
最终的结果是,在Windows上,control-C几乎永远无法工作
唤醒一个阻塞的Python进程,其中有一些特殊的异常
有人为此做了工作。在Python 2上,唯一的函数
实现此功能的是time.sleep()和
多处理.Semaphore.acquire;在Python3上还有一些
(您可以将源代码grep为_PyOS_SigintEvent以查找它们),但是
Thread.join不是其中之一
那么,你能做什么
一种选择是不使用Ctrl-C终止程序,而是使用调用的东西,例如,TerminateProcess
,比如内置的taskkill
工具,或者使用os
模块的Python脚本。但你不想这样
很明显,等到他们在Python 3.8或3.9中提出修复方案,或者在您可以Ctrl-C之前,您的程序是不可接受的
因此,您唯一能做的就是不要阻止thread.join
上的主线程,或者任何其他不可中断的操作
快速肮脏的解决方案是只需轮询join
并超时:
while thread.is_alive():
thread.join(0.2)
现在,您的程序在执行while
循环并调用is_alive
时会短暂中断,然后再返回不间断睡眠200毫秒。在这200毫秒内输入的任何Ctrl-C都将等待您处理它,所以这不是问题
不过200毫秒已经足够长了,足以让人注意到,甚至让人恼火
它可能太短也可能太长。当然,每200毫秒唤醒一次并执行少量Python字节码不会浪费太多CPU,但也不是什么都没有,它仍然在调度程序中获得一个时间片,这可能足以防止笔记本电脑进入长期低功耗模式
最简单的解决办法是找到另一个要阻止的函数。正如Nathaniel J.Smith所说:
你可以用grep搜索源代码,以便找到它们
但可能没有任何东西非常适合。很难想象你会如何设计你的程序来阻止多处理.Semaphore.acquire
,而不会让读者感到非常困惑
在这种情况下,您可能希望直接拖动Win32 API,无论是通过PyWin32
还是ctypes
。看看像time.sleep
和multiprocessing.Semaphore.acquire
这样的函数是如何做到可中断的,阻塞它们正在使用的任何东西,并让线程在退出时发出阻塞的任何信号
如果您愿意使用未记录的CPython内部构件,那么至少在3.7中,隐藏的\u winapi
模块会在您首先等待而不是全部等待时为您添加神奇的\u PyOSSigintEvent
您可以传递给WaitForMultipleObjects
的东西之一是Win32线程句柄,它与join
具有相同的效果,尽管我不确定是否有一种简单的方法可以从Python线程中获取线程句柄
或者,您可以手动创建某种类型的内核同步对象(我不太了解\u winapi
模块,而且我没有Windows系统,因此您可能需要自己阅读源代码,或者至少在交互式解释器中阅读帮助
以查看它提供了什么包装器),WaitForMultipleObjects
,并让线程发出信号。显然,您可以使用taskkill
(或使用os
的Python脚本或第三方psutil
,或任务管理器、进程浏览器等)终止任务。这在这里是不可接受的吗?这是正确的,但我想用Ctrl+C停止它(我更新了这个问题)。你的while循环不等于不在join中设置超时吗?我的意思是,我不明白为什么它在每次执行loop@J.C.Rocamonde0.2秒后,它放弃了join
的尝试,并返回None
。然后我们只是执行字节码返回到循环的顶部,并调用线程.is_alive
,因此我们没有阻塞任何东西,我们可以被检查字节码之间信号的普通CPython代码中断。快速脏的方式对我来说是可以接受的,因为程序将始终在前台运行。我知道这并不完美,但已经足够好了。挖掘Python特定于Windows的源代码或使用未记录的内部代码对我来说太难了。Ctrl+C事件将一直保持到join
超时过期,这让我有点困惑。只要线程在Python退出之前关闭,解释器就不应该尝试join
它。这里是否存在争用条件取决于处理Ctrl+C时程序编排关机的总体方式。正如我所说的,我实际上还没有尝试过让Python挂起的场景。