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

非强制终止python线程的方法介绍

本文给大家分享的是走着的一些强制杀掉python线程经验教训,如果你使用强制手段干掉线程,那么很大几率出现意想不到的bug。请记住一点,锁资源不会因为线程退出而释放锁资源!
本文给大家分享的是走着的一些强制杀掉python线程经验教训,如果你使用强制手段干掉线程,那么很大几率出现意想不到的bug。 请记住一点,锁资源不会因为线程退出而释放锁资源 !

前言:

不要试图用强制方法杀掉一个python线程,这从服务设计上就存在不合理性。 多线程本用来任务的协作并发,如果你使用强制手段干掉线程,那么很大几率出现意想不到的bug。 请记住一点,锁资源不会因为线程退出而释放锁资源 !

我们可以举出两个常见的例子:

1. 有个A线程拿到了锁,因为他是被强制干掉的,没能及时的release()释放锁资源,那么导致所有的线程获取资源是都被阻塞下去,这就是典型的死锁场景。

2.在常见的生产消费者的场景下,消费者从任务队列获取任务,但是被干掉后没有把正在做的任务丢回队列中,那么这就造成了数据丢失。

下面是java和python终止线程的方法:

java有三种方法可以使终止线程:

1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
2. 使用stop方法强行终止线程(不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
3. 使用interrupt方法中断线程。

python可以有两种方法:

1. 退出标记
2. 使用ctypes强行杀掉线程

不管是python还是java环境下,理想的停止退出线程方法是 让线程自个自杀,所谓的线程自杀就是 你给他一个标志位,他退出线程。

下面我们会采用多种方法来测试 停止python线程的异常情况。我们查看一个进程所有的执行线程, 进程是用过掌控资源,线程是用作调度单元,进程要被调度执行必须要有一个线程,默认的线程和进程的pid一样的。

ps -mp 31449 -o THREAD,tid
 
USER   %CPU PRI SCNT WCHAN USER SYSTEM  TID
root   0.0  -  - -     -   -   -
root   0.0 19  - poll_s  -   - 31449
root   0.0 19  - poll_s  -   - 31450

获取到了进程所有的线程后,通过strace得知 31450 是需要我们kill的线程id,当我们kill的时候,会出现整个进程都崩溃的情况。 在多线程环境下,产生的信号是传递给整个进程的,一般而言,所有线程都有机会收到这个信号,进程在收到信号的的线程上下文执行信号处理函数,具体是哪个线程执行的难以获知。也就是说,信号会随机发个该进程的一个线程。

strace -p 31450 Process 31450 attached - interrupt to quit
select(0, NULL, NULL, NULL, {0, 320326}) = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})   = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})   = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})   = ? ERESTARTNOHAND (To be restarted)
--- SIGTERM (Terminated) @ 0 (0) ---
Process 31450 detached

上面出现的问题其实跟pthread的说明是一致的。当我们在python代码里加入 signal 信号处理函数后,回调函数可以防止整个进程的退出,那么问题来了,通过信号函数不能识别你要干掉哪一个线程,也就是说,不能精准的干掉某个线程。你虽然把信号发给31450线程id,但是信号受理人是所属进程的任何一个,另外传给信号处理函数的参数只有信号数和信号stack而已,可有可无的。

加了信号处理后,不会退出进程

select(0, NULL, NULL, NULL, {1, 0})   = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})   = ? ERESTARTNOHAND (To be restarted)
--- SIGTERM (Terminated) @ 0 (0) ---
rt_sigreturn(0xffffffff)        = -1 EINTR (Interrupted system call)
select(0, NULL, NULL, NULL, {1, 0})   = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})   = 0 (Timeout)

如果想从外部通知杀掉某个线程,那么可以构建使用rpc服务,或者别的方式通信,signal信号不可以,因为无法无法传递更多的信息。

python的线程不是模拟的,是真实的内核线程,内核调用pthread方法,但Python上层没有提供关闭线程的方法,这就需要我们自己把握了。强烈推荐使用 event 或者 自定义标志位的方法, 如果非要强制杀掉线程,那么可以用python ctypes PyThreadState SetAsyncExc 方法强制退出,这样对于运行的python服务没有什么影响。

该函数的实现原理比较简单,其实也是在python虚拟机里做个标示位,然后由虚拟机运行一个异常来取消线程,虚拟机会帮你做好try cache。 切记不要在外部杀掉python的某个线程,虽然你能通过ctypes找到线程id,但是你直接kill会干掉整个进程的。

下面的代码是 用ctypes 杀掉线程的样例,不推荐使用,因为太粗暴了.

import ctypes
 
def terminate_thread(thread):
  if not thread.isAlive():
    return
 
  exc = ctypes.py_object(SystemExit)
  res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
    ctypes.c_long(thread.ident), exc)
  if res == 0:
    raise ValueError("nonexistent thread id")
  elif res > 1:
    ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None)
    raise SystemError("PyThreadState_SetAsyncExc failed")

咱们简单look一下PyThreadState源代码,总而言之触发线程的异常模式。 有兴趣的人可以阅读 python pystate.c 的设计,配合着youtube的一些视频分享。

 int
PyThreadState_SetAsyncExc(long id, PyObject *exc) {
  PyInterpreterState *interp = GET_INTERP_STATE();
  ...
  HEAD_LOCK();
  for (p = interp->tstate_head; p != NULL; p = p->next) {
    if (p->thread_id == id) {
      从链表里找到线程的id,避免死锁,我们需要释放head_mutex。
      PyObject *old_exc = p->async_exc;
      Py_XINCREF(exc); #增加该对象的引用数
      p->async_exc = exc; # 更为exc模式
      HEAD_UNLOCK();
      Py_XDECREF(old_exc); # 因为要取消,当然也就递减引用
      ...
      return 1; #销毁线程成功
    }
  }
  HEAD_UNLOCK();
  return 0;
}

原生posix pthread 可以使用 ptread_cancel(tid) 在主线程中结束子线程。但是 Python 的线程库不支持这样做,理由是我们不应该强制地结束一个线程,这样会带来很多隐患,应该让该线程自己结束自己。所以在 Python 中,推荐的方法是在子线程中循环判断一个标志位,在主线程中改变该标志位,子线程读到标志位改变,就结束自己。

类似这个逻辑:

def consumer_threading():
 t1_stop= threading.Event()
 t1 = threading.Thread(target=thread1, args=(1, t1_stop))
 
 t2_stop = threading.Event()
 t2 = threading.Thread(target=thread2, args=(2, t2_stop))
 
 time.sleep(duration)
 #stop the thread2
 t2_stop.set()
 
def thread1(arg1, stop_event):
 while(not stop_event.is_set()):
   #similar to time.sleep()
   stop_event.wait(time)
   pass
 
 
def thread2(arg1, stop_event):
 while(not stop_event.is_set()):
   stop_event.wait(time)
   pass

简单的总结,虽然我们可以用ctypes里的pystats来控制线程,但这种粗暴中断线程的方法是不合理的。 请选用 自杀模式 !如果你的线程正在发生io阻塞,而不能判断事件怎么办? 你的程序需要做优化了,最少在网络io层需要有主动的timeout,避免一直的阻塞下去。


推荐阅读
  • 转自:http:www.yybug.comread-htm-tid-15324.html为什么使用Twisted? 如果你并不准备使用Twisted,你可能有很多异议。为什么使用T ... [详细]
  • Python基础教程:struct模块与格式化字符详解
    本文详细介绍了Python中struct模块的功能,以及如何利用格式化字符实现Python与C语言结构体之间的数据转换。文章通过具体实例讲解了struct模块的主要方法及其应用场景。 ... [详细]
  • python的交互模式怎么输出名文汉字[python常见问题]
    在命令行模式下敲命令python,就看到类似如下的一堆文本输出,然后就进入到Python交互模式,它的提示符是>>>,此时我们可以使用print() ... [详细]
  • 深入解析Hadoop的核心组件与工作原理
    本文详细介绍了Hadoop的三大核心组件:分布式文件系统HDFS、资源管理器YARN和分布式计算框架MapReduce。通过分析这些组件的工作机制,帮助读者更好地理解Hadoop的架构及其在大数据处理中的应用。 ... [详细]
  • 本文详细介绍了在Spring Boot应用中,如何通过`TomcatEmbeddedServletContainerFactory.setTomcatContextCustomizers()`方法来定制和配置嵌入式Tomcat服务器的上下文环境,包括具体的代码示例。 ... [详细]
  • NFS(Network File System)即网络文件系统,是一种分布式文件系统协议,主要用于Unix和类Unix系统之间的文件共享。本文详细介绍NFS的配置文件/etc/exports和相关服务配置,帮助读者理解如何在Linux环境中配置NFS客户端。 ... [详细]
  • 本文探讨了Web开发与游戏开发之间的主要区别,旨在帮助开发者更好地理解两种开发领域的特性和需求。文章基于作者的实际经验和网络资料整理而成。 ... [详细]
  • 在Linux系统上构建Web服务器的详细步骤
    本文详细介绍了如何在Linux系统上搭建Web服务器的过程,包括安装Apache、PHP和MySQL等关键组件,以及遇到的一些常见问题及其解决方案。 ... [详细]
  • 多核处理器技术的显著进展可追溯至IBM于2001年推出的双核RISC处理器POWER4,标志着服务器处理器迈入多核时代。随后,HP和Sun等公司也纷纷加入这一行列,推动了多核处理器在不同领域的广泛应用。 ... [详细]
  • 深入探讨ASP.NET中的OAuth、JWT与OpenID Connect
    本文作为前文关于OAuth2.0和使用.NET实现OAuth身份验证的补充,详细阐述了OAuth与JWT及OpenID Connect之间的关系和差异,旨在提供更全面的理解。 ... [详细]
  • 本文在前文基础上,进一步探讨了如何利用Multi-Paxos算法解决一系列值的共识问题。文中不仅分析了Basic Paxos的局限性,还详细阐述了通过引入领导者节点优化Multi-Paxos算法的具体方法。 ... [详细]
  • Active Object设计模式解析
    Active Object设计模式通过引入代理(Proxy)和命令(Command)模式,实现了方法调用与执行的解耦,从而支持异步操作和提高系统的并发处理能力。本文将深入探讨Active Object模式的工作原理及其应用场景。 ... [详细]
  • Consul 单节点与集群环境构建指南
    本文详细介绍了如何安装和配置 Consul 以支持服务注册与发现、健康检查等功能,包括单节点和集群环境的搭建步骤。 ... [详细]
  • 本文介绍了 ScrapySharp 的基本使用方法,并深入解析了其中的 CssStyleCollection 类。通过 NuGet 包管理器安装 ScrapySharp 后,开发者可以利用其强大的网页抓取功能。文章转载自一位经验丰富的博主。 ... [详细]
  • FreeBSD作为Berkeley Software Distribution (BSD)的一个关键分支,其发展历程深刻影响了现代操作系统的设计。本文将探讨FreeBSD从早期版本到最新版本的技术进步及其对计算行业的贡献。 ... [详细]
author-avatar
冬瓜
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有