
我经常被问到如何杀死一个后台线程,这个问题的答案让很多人不开心: 线程是杀不死的。在本文中,我将向您展示 Python 中用于终止线程的两个选项。
如果我们是一个好奇宝宝的话,可能会遇到这样一个问题,就是:如何杀死一个 Python 的后台线程呢?我们可能尝试解决这个问题,却发现线程是杀不死的。而本文中将展示,在 Python 中用于终止线程的两个方式。
1. 线程无法结束
A Threaded Example
import randomimport threadingimport time
def bg_thread(): for i in range(1, 30): print(f'{i} of 30 iterations...') time.sleep(random.random()) # do some work... print(f'{i} iterations completed before exiting.')
th = threading.Thread(target=bg_thread)th.start()th.join()
$ python thread.py1 of 30 iterations...
2 of 30 iterations...3 of 30 iterations...4 of 30 iterations...5 of 30 iterations...6 of 30 iterations...7 of 30 iterations...^CTraceback (most recent call last): File "thread.py", line 14, in th.join() File "/Users/mgrinberg/.pyenv/versions/3.8.6/lib/python3.8/threading.py", line 1011, in join self._wait_for_tstate_lock() File "/Users/mgrinberg/.pyenv/versions/3.8.6/lib/python3.8/threading.py", line 1027, in _wait_for_tstate_lock elif lock.acquire(block, timeout):KeyboardInterrupt8 of 30 iterations...9 of 30 iterations...10 of 30 iterations...11 of 30 iterations...12 of 30 iterations...13 of 30 iterations...^CException ignored in: Traceback (most recent call last): File "/Users/mgrinberg/.pyenv/versions/3.8.6/lib/python3.8/threading.py", line 1388, in _shutdown lock.acquire()KeyboardInterrupt:
这很奇怪,不是吗?究其原因是,Python 有一些逻辑是会在进程退出前运行的,专门用来等待任何没有被配置为守护线程的后台线程结束,然后再把控制权真正交给操作系统。因此,该进程在其主线程运行时收到到了中断信号,并准备退出。首先,它需要等待后台线程运行结束。但是,这个线程对中断一无所知,这个线程只知道它需要在运行结束前完成 30 次迭代。
Python 在退出过程中使用的等待机制有一个规定,当收到第二个中断信号时,就会中止。这就是为什么第二个 Ctrl-C 会立即结束进程。所以我们看到了,线程是不能被杀死!在下面的章节中,将向展示 Python 中的两个方式,来使线程及时结束。
2. 使用守护进程
Daemon Threads
import randomimport threadingimport time
def bg_thread(): for i in range(1, 30): print(f'{i} of 30 iterations...') time.sleep(random.random()) # do some work... print(f'{i} iterations completed before exiting.')
th = threading.Thread(target=bg_thread)th.daemon = Trueth.start()th.join()
~ $ python x.py1 of 30 iterations...2 of 30 iterations...3 of 30 iterations...4 of 30 iterations...5 of 30 iterations...6 of 30 iterations...^CTraceback (most recent call last): File "thread.py", line 15, in th.join() File "/Users/mgrinberg/.pyenv/versions/3.8.6/lib/python3.8/threading.py", line 1011, in join self._wait_for_tstate_lock() File "/Users/mgrinberg/.pyenv/versions/3.8.6/lib/python3.8/threading.py", line 1027, in _wait_for_tstate_lock
elif lock.acquire(block, timeout):KeyboardInterrupt
3. 使用事件对象
Python Events
使用守护线程,是一种避免在多线程程序中处理意外中断的简单方法,但这是一种只在进程退出的特殊情况下才有效的技巧。不幸的是,有些时候,一个应用程序可能想结束一个线程而不必杀死自己。另外,有些线程可能需要在退出前执行清理工作,而守护线程则不允许这样操作。
那么,还有什么其他选择呢?既然不可能强制线程结束,那么唯一的选择就是给它添加逻辑,让它在被要求退出时自愿退出。有多种方法都可以解决上述问题,但我特别喜欢的一种方法,就是使用一个 Event 对象。
Event 类是由 Python 标准库的线程模块提供,你可以通过实例化类来创建一个事件对象,就像下面这个样子:
exit_event = threading.Event()
import randomimport signal
import threadingimport time
exit_event = threading.Event()
def bg_thread(): for i in range(1, 30): print(f'{i} of 30 iterations...') time.sleep(random.random()) # do some work... if exit_event.is_set(): break print(f'{i} iterations completed before exiting.')
def signal_handler(signum, frame): exit_event.set()
signal.signal(signal.SIGINT, signal_handler)th = threading.Thread(target=bg_thread)th.start()th.join()
$ python thread.py1 of 30 iterations...2 of 30 iterations...3 of 30 iterations...4 of 30 iterations...5 of 30 iterations...6 of 30 iterations...7 of 30 iterations...^C7 iterations completed before exiting.
for i in range(1, 30): print(f'{i} of 30 iterations...')
time.sleep(random.random())
if exit_event.is_set(): break
for i in range(1, 30): print(f'{i} of 30 iterations...') if exit_event.wait(timeout=random.random()): break
4. 总结陈述说明
Conclusion
原文链接:https://www.escapelife.site/posts/558f583c.html
文章转载:Python编程学习圈
(版权归原作者所有,侵删)



点击下方“阅读原文”查看更多