热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

如何“杀死”Python的线程的两个方式

我经常被问到如何杀死一个后台线程,这个问题的答案让很多人不开心:线程是杀不死的。在本文中,我将向您展示 Python 中用于终止线程的两个选项。

我经常被问到如何杀死一个后台线程,这个问题的答案让很多人不开心: 线程是杀不死的。在本文中,我将向您展示 Python 中用于终止线程的两个选项。

 

如果我们是一个好奇宝宝的话,可能会遇到这样一个问题,就是:如何杀死一个 Python 的后台线程呢?我们可能尝试解决这个问题,却发现线程是杀不死的。而本文中将展示,在 Python 中用于终止线程的两个方式。

1. 线程无法结束

· A Threaded Example

下面是一个简单的,多线程的示例代码。

 import random
  import threading
  import 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()

使用下面命令来运行程序,在下面的程序运行中,当跑到第 7 次迭代时,按下 Ctrl-C 来中断程序,发现后台运行的程序并没有终止掉。而在第 13 次迭代时,再次按下 Ctrl-C 来中断程序,发现程序真的退出了。

 $ python thread.py
  1 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):
  KeyboardInterrupt
  8 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

在上面提到过,在 Python 退出之前,它会等待任何非守护线程的线程。而守护线程就是,一个不会阻止 Python 解释器退出的线程。

如何使一个线程成为一个守护线程?所有的线程对象都有一个 daemon 属性,可以在启动线程之前将这个属性设置为 True,然后该线程就会被视为一个守护线程。下面是上面的示例应用程序,修改后守护线程版本:

 import random
  import threading
  import 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 = True
  th.start()
  th.join()

再次运行它,并尝试中断它,发现第一个执行 Ctrl-C 后进程立即就退出了。

~ $ python x.py
  1 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

那么这个线程会发生什么呢?线程继续运行,就像什么都没发生一样,直到 Python 进程终止并返回到操作系统。这时,线程就不存在了。你可能认为这实际上是一种杀死线程的方法,但要考虑到以这种方式杀死线程,你必须同时杀死进程。

3. 使用事件对象

·Python Events

使用守护线程,是一种避免在多线程程序中处理意外中断的简单方法,但这是一种只在进程退出的特殊情况下才有效的技巧。不幸的是,有些时候,一个应用程序可能想结束一个线程而不必杀死自己。另外,有些线程可能需要在退出前执行清理工作,而守护线程则不允许这样操作。

那么,还有什么其他选择呢?既然不可能强制线程结束,那么唯一的选择就是给它添加逻辑,让它在被要求退出时自愿退出。有多种方法都可以解决上述问题,但我特别喜欢的一种方法,就是使用一个 Event 对象。

Event 类是由 Python 标准库的线程模块提供,你可以通过实例化类来创建一个事件对象,就像下面这个样子:

  exit_event = threading.Event()

Event 对象可以处于两种状态之一: set 或 not set。当我们实例化创建之后,默认事件并没有被设置。

· 若要将事件状态更改为 set,则可以调用 set()方法;

· 要查明是否设置了事件,使用 is_set() 方法,设置了则返回 True;

· 还可以使用 wait() 方法等待事件,等待操作阻塞直到设置事件(可以设置超时)

其核心思路,就是在线程需要退出的时候设置事件。然后,线程需要经常地检查事件的状态(通常是在循环中),并在发现事件已经设置时处理自己的终止。对于上面显示的示例,一个好的解决方案是添加一个捕获 Ctrl-C 中断的信号处理程序,而不是突然退出,只需设置事件并让线程优雅地结束。

import random
  import signal
  import threading
  import 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.py
  1 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.

需要注意的是,中断是如何被优雅地处理的,以及线程能够运行在循环之后出现的代码。如果当线程需要在退出之前,关闭文件句柄或数据库连接时,这种方式就非常有用了。其能够在线程退出之前,运行清理代码有时是必要的,以避免资源泄漏。我在上面提到过,event 对象也是可以等待的:

for i in range(1, 30):
   print(f'{i} of 30 iterations...')
   time.sleep(random.random())
   if exit_event.is_set():
   break

在每个迭代中,都有一个对 time.sleep() 的调用,这将阻塞线程。如果在线程 sleep 时设置了退出事件,那么它就不能检查事件的状态,因此在线程能够退出之前会有一个小的延迟。在这种情况下,如果有 sleep,使用 wait() 方法将 sleep 与 event 对象的检查结合起来会更有效:

for i in range(1, 30):
   print(f'{i} of 30 iterations...')
   if exit_event.wait(timeout=random.random()):
   break

这个解决方案有效地为提供了一个可中断的 sleep,因为在线程停留在 wait() 调用的中间时设置了事件,那么等待将立即返回。

4. 总结陈述说明

·Conclusion

你知道 Python 中的 event 对象吗?它们是比较简单的同步原语之一,不仅可以用作退出信号,而且在线程需要等待某些外部条件发生的许多其他情况下也可以使用。

大家如果本文涉及的代码感兴趣,可以 即可获取对应代码文件。

下面是配套资料,对于做【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!

相对应的视频学习教程免费分享!563251944,其中包括了有基础知识、Linux必备、Shell、互联网程序原理、Mysql数据库、抓包工具专题、接口测试工具、测试进阶-Python编程、Web自动化测试、APP自动化测试、接口自动化测试、测试高级持续集成、测试架构开发测试框架、性能测试、安全测试等。

学习不要孤军奋战,最好是能抱团取暖,相互成就一起成长,群众效应的效果是非常强大的,大家一起学习,一起打卡,会更有学习动力,也更能坚持下去。你可以加入我们的测试技术交流扣扣群

 


版权声明:本文为CSGO55678原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/CSGO55678/article/details/121439035
推荐阅读
  • 选择适合生产环境的Docker存储驱动
    本文旨在探讨如何在生产环境中选择合适的Docker存储驱动,并详细介绍不同Linux发行版下的配置方法。通过参考官方文档和兼容性矩阵,提供实用的操作指南。 ... [详细]
  • 开发笔记:9.八大排序
    开发笔记:9.八大排序 ... [详细]
  • 对象自省自省在计算机编程领域里,是指在运行时判断一个对象的类型和能力。dir能够返回一个列表,列举了一个对象所拥有的属性和方法。my_list[ ... [详细]
  • 在创建新的Android项目时,您可能会遇到aapt错误,提示无法打开libstdc++.so.6共享对象文件。本文将探讨该问题的原因及解决方案。 ... [详细]
  • Python处理Word文档的高效技巧
    本文详细介绍了如何使用Python处理Word文档,涵盖从基础操作到高级功能的各种技巧。我们将探讨如何生成文档、定义样式、提取表格数据以及处理超链接和图片等内容。 ... [详细]
  • 本文介绍如何使用 Android 的 Canvas 和 View 组件创建一个简单的绘图板应用程序,支持触摸绘画和保存图片功能。 ... [详细]
  • 本文介绍如何使用 Angular 6 的 HttpClient 模块来获取 HTTP 响应头,包括代码示例和常见问题的解决方案。 ... [详细]
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • 深入解析 Android IPC 中的 Messenger 机制
    本文详细介绍了 Android 中基于消息传递的进程间通信(IPC)机制——Messenger。通过实例和源码分析,帮助开发者更好地理解和使用这一高效的通信工具。 ... [详细]
  • Java多线程实现:从1到100分段求和并汇总结果
    本文介绍如何使用Java编写一个程序,通过10个线程分别计算不同区间的和,并最终汇总所有线程的结果。每个线程负责计算一段连续的整数之和,最后将所有线程的结果相加。 ... [详细]
  • 深入解析Java多线程与并发库的应用:空中网实习生面试题详解
    本文详细探讨了Java多线程与并发库的高级应用,结合空中网在挑选实习生时的面试题目,深入分析了相关技术要点和实现细节。文章通过具体的代码示例展示了如何使用Semaphore和SynchronousQueue来管理线程同步和任务调度。 ... [详细]
  • 本文详细探讨了HTML表单中GET和POST请求的区别,包括它们的工作原理、数据传输方式、安全性及适用场景。同时,通过实例展示了如何在Servlet中处理这两种请求。 ... [详细]
  • 利用决策树预测NBA比赛胜负的Python数据挖掘实践
    本文通过使用2013-14赛季NBA赛程与结果数据集以及2013年NBA排名数据,结合《Python数据挖掘入门与实践》一书中的方法,展示如何应用决策树算法进行比赛胜负预测。我们将详细讲解数据预处理、特征工程及模型评估等关键步骤。 ... [详细]
  • 利用Selenium与ChromeDriver实现豆瓣网页全屏截图
    本文介绍了一种使用Selenium和ChromeDriver结合Python代码,轻松实现对豆瓣网站进行完整页面截图的方法。该方法不仅简单易行,而且解决了新版Selenium不再支持PhantomJS的问题。 ... [详细]
  • LeetCode 690:计算员工的重要性评分
    在解决LeetCode第690题时,我记录了详细的解题思路和方法。该问题要求根据员工的ID计算其重要性评分,包括直接和间接下属的重要性。本文将深入探讨如何使用哈希表(Map)来高效地实现这一目标。 ... [详细]
author-avatar
a52713849_937
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有