不要用强制方法杀掉python线程


Posted in Python onFebruary 26, 2017

前言:

    不要试图用强制方法杀掉一个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 <span style="font-size:14px;line-height:21px;">31450</span> Process <span style="font-size:14px;line-height:21px;">31450</span> 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 <span style="font-size:14px;line-height:21px;">31450</span> 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,避免一直的阻塞下去。

Python 相关文章推荐
浅析Python多线程下的变量问题
Apr 28 Python
Python中将字典转换为XML以及相关的命名空间解析
Oct 15 Python
玩转python selenium鼠标键盘操作(ActionChains)
Apr 12 Python
Python2随机数列生成器简单实例
Sep 04 Python
numpy.random.seed()的使用实例解析
Feb 03 Python
详解Django解决ajax跨域访问问题
Aug 24 Python
Django csrf 验证问题的实现
Oct 09 Python
selenium2.0中常用的python函数汇总
Aug 05 Python
python爬取Ajax动态加载网页过程解析
Sep 05 Python
Python 如何优雅的将数字转化为时间格式的方法
Sep 26 Python
浅谈python3 构造函数和析构函数
Mar 12 Python
Python连接HDFS实现文件上传下载及Pandas转换文本文件到CSV操作
Jun 06 Python
Python实现 多进程导入CSV数据到 MySQL
Feb 26 #Python
python检查URL是否正常访问的小技巧
Feb 25 #Python
python解析基于xml格式的日志文件
Feb 25 #Python
Python中防止sql注入的方法详解
Feb 25 #Python
Python 数据结构之旋转链表
Feb 25 #Python
Python数据结构之翻转链表
Feb 25 #Python
浅析python中SQLAlchemy排序的一个坑
Feb 24 #Python
You might like
php5.2时间相差8小时
2007/01/15 PHP
destoon供应信息title调用出公司名称的方法
2014/08/22 PHP
PHP常用的排序和查找算法
2015/08/06 PHP
Thinkphp5框架简单实现钩子(Hook)行为的方法示例
2019/09/03 PHP
基于jquery自定义图片热区效果
2012/07/21 Javascript
HTML Color Picker(js拾色器效果)
2013/08/27 Javascript
JavaScript中的console.trace()函数介绍
2014/12/29 Javascript
ionic实现可滑动的tab选项卡切换效果
2020/04/15 Javascript
angular过滤器实现排序功能
2017/06/27 Javascript
Vue项目webpack打包部署到服务器的实例详解
2017/07/17 Javascript
webpack+vue中使用别名路径引用静态图片地址
2017/11/20 Javascript
Vue 页面状态保持页面间数据传输的一种方法(推荐)
2018/11/01 Javascript
微信小程序实用代码段(收藏版)
2019/12/17 Javascript
JavaScript简易计算器制作
2020/01/17 Javascript
element-ui树形控件后台返回的数据+生成组织树的工具类
2020/03/05 Javascript
vue实现前端分页完整代码
2020/06/17 Javascript
JavaScript位置参数实现原理及过程解析
2020/09/14 Javascript
[49:43]VG vs FNATIC 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/17 DOTA
python求众数问题实例
2014/09/26 Python
Python 调用 zabbix api的方法示例
2019/01/06 Python
python面试题小结附答案实例代码
2019/04/11 Python
详解python运行三种方式
2019/05/13 Python
华为2019校招笔试题之处理字符串(python版)
2019/06/25 Python
Python 实现一行输入多个数字(用空格隔开)
2020/04/29 Python
Python字典取键、值对的方法步骤
2020/09/30 Python
Aerosoles爱柔仕官网:美国舒软女鞋品牌
2017/07/17 全球购物
Python面试题:Python是如何进行内存管理的
2014/08/04 面试题
出纳试用期自我鉴定
2014/04/07 职场文书
计算机应用专业毕业生求职信
2014/06/03 职场文书
建筑安全标语
2014/06/07 职场文书
领导班子个人对照检查材料(群众路线)
2014/09/26 职场文书
毕业论文指导教师评语
2014/12/30 职场文书
八一建军节慰问信
2015/02/14 职场文书
《丑小鸭》教学反思
2016/02/19 职场文书
详解在OpenCV中如何使用图像像素
2022/03/03 Python
HTML5页面打开微信小程序功能实现
2022/09/23 HTML / CSS