Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/356.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
停止Windows上的多线程Python脚本_Python_Windows_Multithreading_Python Multithreading - Fatal编程技术网

停止Windows上的多线程Python脚本

停止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

我在使用简单的多线程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=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挂起的场景。