渣渣飞

渣渣飞,长年在票圈深夜放毒,是网易游戏高级运维工程师,对代码性能及系统原理饶有兴趣,三人行,必有我师。现负责监控相关业务开发。

前言

使用 Python 都不会错过线程这个知识,但是每次谈到线程,大家都下意识说 GIL 全局锁,

但其实除了这个老生常谈的话题,还有很多有价值的东西可以探索的,譬如:setDaemon()。

线程的使用 与 存在的问题

我们会写这样的代码来启动多线程:

1import time

2import threading

3

4def test():

5 while True:

6 print threading.currentThread()

7 time.sleep(1)

8

9if __name__ == '__main__':

10 t1 = threading.Thread(target=test)

11 t2 = threading.Thread(target=test)

12 t1.start()

13 t2.start()

输出:

1^C

2

3^C^C^C^C^C^C # ctrl-c 多次都无法中断

4

5^C

6

7

8

9

10...(两个线程竞相打印)

通过 Threading 我们可以很简单的实现并发的需求,但是同时也给我们带来了一个大难题: 怎么退出呢?

在上面的程序运行中,我已经尝试按了多次的 ctrl-c,都无法中断这程序工作的热情!最后是迫不得已用 kill 才结束。

那么怎样才能可以避免这种问题呢?或者说,怎样才能在主线程退出的时候,子线程也自动退出呢?

守护线程

有过相似经验的老司机肯定就知道,setDaemon() 将线程搞成 守护线程 不就得了呗:

1import time

2import threading

3

4def test():

5 while True:

6 print threading.currentThread()

7 time.sleep(1)

8

9if __name__ == '__main__':

10 t1 = threading.Thread(target=test)

11 t1.setDaemon(True)

12 t1.start()

13

14 t2 = threading.Thread(target=test)

15 t2.setDaemon(True)

16 t2.start()

输出:

1python2.7 1.py

2

3

4(直接退出了)

直接退出?理所当然,因为主线程已经执行完了,确实是已经结束了,正因为设置了守护线程,所以这时候子线程也一并退出了。

突如其来的 daemon

那么问题来了,我们以前学 C 语言的时候,好像不用 Daemon 也可以啊,比如这个:

1#include

2#include

3#include

4

5void *test(void *args)

6{

7 while (1)

8 {

9 printf("ThreadID: %d\n", syscall(SYS_gettid));

10 sleep(1);

11 }

12}

13

14int main()

15{

16 pthread_t t1 ;

17 int ret = pthread_create(&t1, NULL, test, NULL);

18 if (ret != 0)

19 {

20 printf("Thread create failed\n");

21 }

22

23 // 避免直接退出

24 sleep(2);

25 printf("Main run..\n");

26}

输出:

1# gcc -lpthread test_pytha.out & ./a

2ThreadID: 31233

3ThreadID: 31233

4Main run.. (毫不犹豫退出了)

既然 Python 也是用 C 写的,为什么 Python 多线程退出需要 setDaemon ???

想要解决这个问题,我们怕不是要从主线程退出的一刻开始讲起,从前….

反藤摸瓜

Python 解析器在结束的时候,会调用 wait_for_thread_shutdown 来做个例行清理:

1// python2.7/python/pythonrun.c

2

3static void

4wait_for_thread_shutdown(void)

5{

6#ifdef WITH_THREAD

7 PyObject *result;

8 PyThreadState *tstate = PyThreadState_GET();

9 PyObject *threading = PyMapping_GetItemString(tstate->interp->modules,

10 "threading");

11 if (threading == NULL) {

12 /* threading not imported */

13 PyErr_Clear();

14 return;

15 }

16 result = PyObject_CallMethod(threading, "_shutdown", "");

17 if (result == NULL)

18 PyErr_WriteUnraisable(threading);

19 else

20 Py_DECREF(result);

21 Py_DECREF(threading);

22#endif

23}

我们看到 #ifdef WITH_THREAD 就大概猜到对于是否多线程,这个函数是运行了不同的逻辑的

很明显,我们上面的脚本,就是命中了这个线程逻辑,所以它会动态 import threading 模块,然后执行 _shutdown 函数。

这个函数的内容,我们可以从 threading 模块看到:

1# /usr/lib/python2.7/threading.py

2

3_shutdown = _MainThread()._exitfunc

4

5class _MainThread(Thread):

6

7 def __init__(self):

8 Thread.__init__(self, name="MainThread")

9 self._Thread__started.set()

10 self._set_ident()

11 with _active_limbo_lock:

12 _active[_get_ident()] = self

13

14 def _set_daemon(self):

15 return False

16

17 def _exitfunc(self):

18 self._Thread__stop()

19 t = _pickSomeNonDaemonThread()

20 if t:

21 if __debug__:

22 self._note("%s: waiting for other threads", self)

23 while t:

24 t.join()

25 t = _pickSomeNonDaemonThread()

26 if __debug__:

27 self._note("%s: exiting", self)

28 self._Thread__delete()

29

30def _pickSomeNonDaemonThread():

31 for t in enumerate():

32 if not t.daemon and t.is_alive():

33 return t

34 return None

_shutdown 实际上也就是 _MainThread()._exitfunc 的内容,主要是将 enumerate() 返回的所有结果,全部 join() 回收

而 enumerate() 是什么?

这个平时我们也会使用,就是当前进程的所有 符合条件 的 Python线程对象:

1>>> print threading.enumerate()

2[<_mainthread started>]

1# /usr/lib/python2.7/threading.py

2

3def enumerate():

4 """Return a list of all Thread objects currently alive.

5

6 The list includes daemonic threads, dummy thread objects created by

7 current_thread(), and the main thread. It excludes terminated threads and

8 threads that have not yet been started.

9

10 """

11 with _active_limbo_lock:

12 return _active.values() + _limbo.values()

符合条件???符合什么条件??不着急,容我娓娓道来:

从起源谈存活条件

在 Python 的线程模型里面,虽然有 GIL 的干涉,但是线程却是实实在在的原生线程

Python 只是多加一层封装: t_bootstrap,然后再在这层封装里面执行真正的处理函数。

在 threading 模块内,我们也能看到一个相似的:

1# /usr/lib/python2.7/threading.py

2

3class Thread(_Verbose):

4 def start(self):

5 ...省略

6 with _active_limbo_lock:

7 _limbo[self] = self # 重点

8 try:

9 _start_new_thread(self.__bootstrap, ())

10 except Exception:

11 with _active_limbo_lock:

12 del _limbo[self] # 重点

13 raise

14 self.__started.wait()

15

16 def __bootstrap(self):

17 try:

18 self.__bootstrap_inner()

19 except:

20 if self.__daemonic and _sys is None:

21 return

22 raise

23

24 def __bootstrap_inner(self):

25 try:

26 ...省略

27 with _active_limbo_lock:

28 _active[self.__ident] = self # 重点

29 del _limbo[self] # 重点

30 ...省略

在上面的一连串代码中,_limbo 和 _active 的变化都已经标记了重点,我们可以得到下面的定义:

1 _limbo : 就是调用了 start,但是还没来得及 _start_new_thread 的对象

2 _active: 活生生的线程对象

那么回到上文,当 _MainThread()._exitfunc 执行时,是会检查整个进程是否存在 _limbo + _active 的对象,

只要存在一个,就会调用 join(), 这个也就是堵塞的原因。

setDaemon 用处

无限期堵塞不行,自作聪明帮用户强杀线程也不是办法,那么怎么做才会比较优雅呢?

那就是提供一个途径,让用户来设置随进程退出的标记,那就是 setDaemon:

1class Thread():

2 ...省略

3 def setDaemon(self, daemonic):

4 self.daemon = daemonic

5

6 ...省略

7

8# 其实上面也贴了,这里再贴一次

9def _pickSomeNonDaemonThread():

10 for t in enumerate():

11 if not t.daemon and t.is_alive():

12 return t

13 return None

只要子线程,全部设置 setDaemon(True), 那么主线程一准备退出,全都乖乖地由操作系统销毁回收。

之前一直很好奇,pthread 都没有 daemon 属性,为什么 Python 会有呢?

结果这玩意就是真的是仅作用于 Python 层(手动笑脸)

结语

区区一个 setDaemon 可以引出很多本质内容的探索机会,比如线程的创建过程,管理流程等。

这些都是很有意思的内容,我们应该大胆探索,不局限于使用~

往期精彩

python threading setdaemon_Python线程为什么搞个setDaemon相关推荐

  1. python 多线程 setdaemon_Python线程join和setDaemon

    看一下线程的setDaemon()方法 importtimeimportthreadingimportctypesimportinspectdefsayHello():for i in range(1 ...

  2. python threading 结束线程

    python threading 启动的线程,并没有提供终止线程的方法,现总结一下在网上找到的方法 1.通过threading.Thread._Thread__stop()结束线程 import ti ...

  3. python threading setdaemon_Python setdaemon守护进程

    setdaemon守护进程#_*_coding:utf-8_*_ __author__ = 'gaogd' import time import threading ''' 守护进程,如果主线程dow ...

  4. python 多线程 setdaemon_python多线程之t.setDaemon(True) 和 t.join()

    0.目录 1.参考 2.结论 (1)通过 t.setDaemon(True) 将子线程设置为守护进程(默认False),主线程代码执行完毕后,python程序退出,无需理会守护子线程的状态. (2) ...

  5. 潇洒郎: python threading 实现线程暂停、恢复、停止功能

    结果: 线程暂停.恢复.停止见注释 obj-T1-0obj-T2-0main 0obj-T1-1obj-T2-1obj-T2-2main 1obj-T1-2obj-T1-3obj-T2-3main 2 ...

  6. python 中获取线程id

    该问题的解决主要参考了网上的几篇文章,在此一并谢过. 1.python下使用ctypes获取threading线程id python的多线程坑坑不断- - python的threading因为封装的太 ...

  7. python 多线程 setdaemon_python使用Thread的setDaemon启动后台线程教程

    多线程编程当中, 线程的存在形态比较抽象. 通过前台线程后台线程, 可以有效理解线程运行顺序.(复杂的多线程程序可以通过设置线程优先级实现) 后台线程与前台线程的直接区别是, 1)setDaemon( ...

  8. Python之进程+线程+协程(进程的本质 与 threading线程模块)

    文章目录 基本概念 threading线程模块 本篇开始分析Python中的并发程序,也就是进程.线程.协程序的使用.由于是用自己的语言总结的,因此比较大白话,或者叫通俗易懂.而且很多细节方面没有具体 ...

  9. Python 批量创建线程及threading.Thread类的常用函数及方法

    在<[Python]线程的创建.执行.互斥.同步.销毁>(点击打开链接)中介绍了Python中线程的使用,但是里面线程的创建,使用了很原始的方式,一行代码创建一条.其实,Python里是可 ...

最新文章

  1. 程序员修炼之道阅读笔记01
  2. SQL Server 2008连载之存储结构——基本系统视图
  3. linux javaweb环境单价,linux(centos)下Java Web环境开发
  4. java断点续传插件_视频断点续传+java视频
  5. [vue] 如何解决vue打包vendor过大的问题?
  6. tr069相关协议说明
  7. java取模运算_Java的四则运算符与取模运算符
  8. 地平线开源轻量级、有效可变组卷积的人脸识别网络VarGFaceNet
  9. 插画素材|萌化!超可爱的动物主题手绘复古插画
  10. C# Winform开发框架源码 Winform系统开发 图书借阅系统,图书管理系统,说明文档齐全
  11. arm neon介绍
  12. Storm并发度详解(转载)
  13. springBoot简单使用SpringData的jdbc和简单使用durid
  14. JAVA后台,对上传资源限定大小
  15. Base64使用案例
  16. 四天搞懂生成对抗网络(三)——用CGAN做图像转换的鼻祖pix2pix
  17. 【读书笔记】Python编程:从入门到实践-埃里克·马瑟斯,python基础体系巩固和常见场景练习
  18. linux mint 安装ubuntu软件中心,Ubuntu和Linux Mint:安装Pinta 1.6工具
  19. dotnet OpenXML 文本 Kerning 字间距的作用
  20. 利用python做薪酬管理_HRD告诉你,6个薪酬体系策略做好薪酬管理

热门文章

  1. C++:setw及setfill
  2. Ubuntu10.10 安装scim
  3. [乐意黎原创] 道德姐(daodejie)及其由来
  4. 竞选计算机课代表稿子,竞选数学课代表演讲稿精选五篇
  5. vue原理:vue中是如何监听数组变化?
  6. 移动IPv6光猫各个lan口的作用,移动光猫lan3口可以设置上网吗?
  7. 2016年甲骨文全球大会创造极致云体验
  8. python飞机大战类_Python版飞机大战
  9. A段架构设计_隽语集(IT+設計思考_1901)
  10. table 缩小行间距_table中设置tr行间距详解