停止一个线程:使用标志 vs. Event
停止一个线程:使用标志 vs. Event
我看到一些使用Event
来停止线程的示例,我认为使用布尔标志会更简单。
Event
class MyThread(threading.Thread): def __init__(self): self._please_stop = threading.Event() def run(self): while not self._please_stop.is_set(): [...] def stop(self): self._please_stop.set()
Flag
class MyThread(threading.Thread): def __init__(self): self._please_stop = False def run(self): while not self._please_stop: [...] def stop(self): self._please_stop = True
在这里使用Event
有什么好处?它的wait
方法并没有被使用。为什么它比布尔标志更好?
我可以理解如果同一个Event
在多个线程之间共享,但是除此之外,我无法理解它的好处。
这个邮件列表线程表明Event
会更安全,但我不清楚为什么。
更准确地说,我不明白以下两段话:
如果我对全局解释器锁(GIL)的理解是正确的,它会同步对Python数据结构的所有访问(比如我的布尔标志'terminated')。如果是这样,为什么要使用
threading.Event
来实现这个目的?全局解释器锁是一个实现细节,依赖它来同步事物并不具有未来性。你可能会得到很多警告,但使用
threading.Event()
并不更难,而且从长远来看更正确和更安全。
我同意使用Event
几乎没有额外的开销,所以我可以坚持使用它,但我想了解布尔标志方法的限制。
(我使用的是Python3,所以我不关心Python2的限制,如果有的话,也值得在这里提到。)
原因:问题的出现是因为在Python中设置一个boolean变量不一定是原子操作,尽管在当前的Python版本中有一个全局锁(GIL)可以使得设置属性的操作看起来是原子的,但是这个锁在未来可能会被取消。使用Event可以使操作变为原子操作,因为它使用自己的锁来进行访问。
解决方法:使用Event来确保操作的原子性。另外,可以通过阅读关于GIL的更多资料来了解更多关于Python的线程操作的细节。
以下是一个示例代码,展示了如何使用Event来停止一个线程:
import threading class MyThread(threading.Thread): def __init__(self): super().__init__() self.stop_event = threading.Event() def run(self): while not self.stop_event.is_set(): # do something pass def stop(self): self.stop_event.set() # 创建并启动线程 my_thread = MyThread() my_thread.start() # 停止线程 my_thread.stop()
通过调用`stop()`方法来设置Event,从而停止线程的运行。在线程的`run()`方法中,使用`self.stop_event.is_set()`来检查是否需要停止线程的运行。
通过使用Event来实现线程的停止操作,可以确保操作的原子性。需要注意的是,设置boolean变量的操作不一定是原子的,因此在编写线程相关的代码时,需要考虑到这一点。
在编程中,通常不仅仅是让代码在今天正常工作,还要确保在将来的更改中也能保持正常工作。
- 其他Python实现没有全局解释器锁(GIL)。我明天会在pypy上运行它吗?
- 实际上,我需要将工作分散到几个进程中。使用multiprocessing...但如果只使用局部变量,它将失败。
- 结果发现只有在其他几个线程认为应该停止时,代码才应该停止。那么,使用Semaphore代替Event...但使用变量实现可能容易出错。
所以,很可能你可以在Python中编写多线程程序,并依赖于字节码的中断和GIL的释放...但如果以后我要阅读和更改你的代码,如果你使用标准的同步原语,我会更加愉快。
谢谢。正如我在回答Mad Physicist的回答时所写的,这里的问题是我总是假设设置布尔值是原子的,所以我没有遇到GIL问题。我完全同意依赖于单一实现是不理想的,特别是如果采用其他方式是那么简单的话。我也同意关于未来代码的可维护性(因此我的问题)。