0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

在Python中用于终止线程的两个选项

马哥Linux运维 来源:Escape 作者:Escape 2021-11-17 10:02 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

我经常被问到如何杀死一个后台线程,这个问题的答案让很多人不开心: 线程是杀不死的。在本文中,我将向您展示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()
  • 使用下面命令来运行程序,在下面的程序运行中,当跑到第7次迭代时,按下Ctrl-C来中断程序,发现后台运行的程序并没有终止掉。而在第13次迭代时,再次按下Ctrl-C来中断程序,发现程序真的退出了。

$ 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

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

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

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()
  • 再次运行它,并尝试中断它,发现第一个执行Ctrl-C后进程立即就退出了。

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


3. 使用事件对象

Python Events

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

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

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

exit_event = threading.Event()
  • Event对象可以处于两种状态之一:setnot set。当我们实例化创建之后,默认事件并没有被设置。

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

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

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

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

import randomimport signalimport 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.
  • 需要注意的是,中断是如何被优雅地处理的,以及线程能够运行在循环之后出现的代码。如果当线程需要在退出之前,关闭文件句柄或数据库连接时,这种方式就非常有用了。其能够在线程退出之前,运行清理代码有时是必要的,以避免资源泄漏。我在上面提到过,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()方法将sleepevent对象的检查结合起来会更有效:

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

原文链接:https://www.escapelife.site/posts/558f583c.html

责任编辑:haq
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • python
    +关注

    关注

    57

    文章

    4856

    浏览量

    89558
  • 线程
    +关注

    关注

    0

    文章

    508

    浏览量

    20755

原文标题:如何杀死一个Python线程

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    FreeRtos 能否同时使用两个 CPU?

    的情况下,CM0 更愿意专门用于管理外设。 - 是否有 CM0 和 CM4 中同时运行代码的简单示例或教程? - FreeRtos 能否同时使用两个 CPU?
    发表于 11-11 08:28

    Python调用API教程

    两个不同系统之间的信息交互。在这篇文章中,我们将详细介绍Python调用API的方法和技巧。 一、用Requests库发送HTTP请求 使用Python调用API的第一步是发送HTTP请求,通常
    的头像 发表于 11-03 09:15 320次阅读

    RTThread线程退出后rt_malloc动态创建的资源没有释放怎么解决?

    测试过程中,线程中用rt_malloc动态创建4KB的资源,在线程运行过程中用rt_thr
    发表于 10-13 07:06

    rtt中建两个线程a和b,怎么确保线程a执行完立刻切到线程b?

    怎么获取从线程开始切换到切换完成用的总的CPU时钟节拍数量?
    发表于 10-10 06:37

    硬件SPI两个CS操作两个norflash,怎么互斥操作两个norflash?

    硬件SPI两个CS操作两个norflash,怎么互斥操作两个norflash,有一norflash被模拟成U盘,会在中断中操作spi。
    发表于 09-26 06:18

    基本半导体连获两个行业奖项

    近日,基本半导体凭借碳化硅模块领域的突出表现,连获“国产SiC模块TOP企业奖”和“年度优秀功率器件产品奖”两个行业奖项。
    的头像 发表于 09-05 16:31 886次阅读

    【HZ-T536开发板免费体验】—— linux创建线程

    自己的私有资源。 linux系统中,线程状态通常反映了当前线程的当前活动和执行阶段。 主要分为: 1。运行转态 2。阻塞转态 3。终止状态 如何区分单
    发表于 09-01 21:31

    看到STM8L152用两个IO用两个或非门检测两个通断,是什么原理呢?

    图中两个按键开关是两个干簧管,为什么不直接对GND设计来检测这个干簧管通断呢? 这样设计的原理是什么?
    发表于 06-12 06:25

    用于四频 GSM / GPRS / EDGE 的 Tx-Rx FEM,带两个 Rx 交换机端口和双频 TD-SCDMA skyworksinc

    电子发烧友网为你提供()用于四频 GSM / GPRS / EDGE 的 Tx-Rx FEM,带两个 Rx 交换机端口和双频 TD-SCDMA相关产品参数、数据手册,更有用于四频 GSM
    发表于 05-29 18:31
    <b class='flag-5'>用于</b>四频 GSM / GPRS / EDGE 的 Tx-Rx FEM,带<b class='flag-5'>两个</b> Rx 交换机端口和双频 TD-SCDMA skyworksinc

    请问如何在Python中实现多线程与多进程的协作?

    大家好!我最近在开发一Python项目时,需要同时处理多个任务,且每个任务需要不同的计算资源。我想通过多线程和多进程的组合来实现并发,但遇到了一些问题。 具体来说,我有两个任务,一
    发表于 03-11 06:57

    创建了用于OpenVINO™推理的自定义C++和Python代码,从C++代码中获得的结果与Python代码不同是为什么?

    创建了用于OpenVINO™推理的自定义 C++ 和 Python* 代码。 两个推理过程中使用相同的图像和模型。 从 C++ 代码中获得的结果与
    发表于 03-06 06:22

    ADS828中有两个输入管脚IN+和IN-,当两个管脚的都接不同的输入的时候,输入值是如何计算的啊?

    ADS828中有两个输入管脚IN+和IN-,当两个管脚的都接不同的输入的时候,输入值是如何计算的啊?是等于IN+的输入电压减去IN-的电压吗?
    发表于 02-06 06:25

    两个不同频率晶振靠的近会怎样

    晶振的振荡本质上是一种机械振动(压电晶体层面)。当两个晶振靠得很近时,它们的机械振动可能会相互影响。一晶振的振动可能会通过电路板或者外壳等介质传递给另一晶振,从而改变另一
    的头像 发表于 01-20 13:55 2017次阅读
    <b class='flag-5'>两个</b>不同频率晶振靠的近会怎样

    两个晶体管能如何实现高效正弦波振荡?

    电子设计中,振荡器是一重要的组件,广泛应用于信号发生、无线通信和音频设备中。一般来说,设计一稳定的正弦波振荡器往往需要运算放大器或复杂的电路结构。然而,是否可以仅用
    的头像 发表于 01-07 12:00 947次阅读
    <b class='flag-5'>两个</b>晶体管能如何实现高效正弦波振荡?

    使用TPS546C23两个独立的单相评估模块

    电子发烧友网站提供《使用TPS546C23两个独立的单相评估模块.pdf》资料免费下载
    发表于 12-07 14:08 0次下载
    使用TPS546C23<b class='flag-5'>两个</b>独立的单相评估模块