python关闭线程daemon_Python:线程为什么搞个setDaemon

前言

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

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

线程的使用 与 存在的问题

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

import time

import threading

def test():

while True:

print threading.currentThread()

time.sleep(1)

if __name__ == '__main__':

t1 = threading.Thread(target=test)

t2 = threading.Thread(target=test)

t1.start()

t2.start()

输出:

^C

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

^C

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

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

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

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

守护线程

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

import time

import threading

def test():

while True:

print threading.currentThread()

time.sleep(1)

if __name__ == '__main__':

t1 = threading.Thread(target=test)

t1.setDaemon(True)

t1.start()

t2 = threading.Thread(target=test)

t2.setDaemon(True)

t2.start()

输出:

python2.7 1.py

(直接退出了)

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

突如其来的 daemon

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

#include

#include

#include

void *test(void *args)

{

while (1)

{

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

sleep(1);

}

}

int main()

{

pthread_t t1 ;

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

if (ret != 0)

{

printf("Thread create failed\n");

}

// 避免直接退出

sleep(2);

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

}

输出:

# gcc -lpthread test_pytha.out & ./a

ThreadID: 31233

ThreadID: 31233

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

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

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

反藤摸瓜

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

// python2.7/python/pythonrun.c

static void

wait_for_thread_shutdown(void)

{

#ifdef WITH_THREAD

PyObject *result;

PyThreadState *tstate = PyThreadState_GET();

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

"threading");

if (threading == NULL) {

/* threading not imported */

PyErr_Clear();

return;

}

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

if (result == NULL)

PyErr_WriteUnraisable(threading);

else

Py_DECREF(result);

Py_DECREF(threading);

#endif

}

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

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

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

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

_shutdown = _MainThread()._exitfunc

class _MainThread(Thread):

def __init__(self):

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

self._Thread__started.set()

self._set_ident()

with _active_limbo_lock:

_active[_get_ident()] = self

def _set_daemon(self):

return False

def _exitfunc(self):

self._Thread__stop()

t = _pickSomeNonDaemonThread()

if t:

if __debug__:

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

while t:

t.join()

t = _pickSomeNonDaemonThread()

if __debug__:

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

self._Thread__delete()

def _pickSomeNonDaemonThread():

for t in enumerate():

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

return t

return None

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

而 enumerate() 是什么?

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

>>> print threading.enumerate()

[<_mainthread started>]

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

def enumerate():

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

The list includes daemonic threads, dummy thread objects created by

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

threads that have not yet been started.

"""

with _active_limbo_lock:

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

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

从起源谈存活条件

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

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

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

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

class Thread(_Verbose):

def start(self):

...省略

with _active_limbo_lock:

_limbo[self] = self # 重点

try:

_start_new_thread(self.__bootstrap, ())

except Exception:

with _active_limbo_lock:

del _limbo[self] # 重点

raise

self.__started.wait()

def __bootstrap(self):

try:

self.__bootstrap_inner()

except:

if self.__daemonic and _sys is None:

return

raise

def __bootstrap_inner(self):

try:

...省略

with _active_limbo_lock:

_active[self.__ident] = self # 重点

del _limbo[self] # 重点

...省略

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

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

_active: 活生生的线程对象

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

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

setDaemon 用处

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

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

class Thread():

...省略

def setDaemon(self, daemonic):

self.daemon = daemonic

...省略

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

def _pickSomeNonDaemonThread():

for t in enumerate():

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

return t

return None

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

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

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

结语

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

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

欢迎各位大神指点交流, QQ讨论群: 258498217

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值