当前位置: 首页 > Linux

Python:线程为什么要setDaemon

时间:2023-04-06 23:52:57 Linux

前言使用Python的你绝对不会错过线程的知识,但是每次说到线程,大家都会下意识的说到GIL全局锁,但其实除了这个老生常谈的话题,还有很多有价值的东西可以探索,比如:setDaemon()。线程的使用和存在的问题我们会写这样的代码来启动多线程: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()把线程变成守护线程:importtimeimportthreadingdeftest():whileTrue:printthreading.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。71.py(exiteddirectly)直接退出?当然,因为主线程执行完了,所以确实结束了。因为设置了守护线程,此时子线程也退出了。突然出现的守护进程是问题所在。以前学C语言的时候,好像不用Daemon也行,比如这样:#include#include#includevoid*test(void*args){while(1){printf("ThreadID:%d\n",syscall(SYS_gettid));睡觉(1);}}intmain(){pthread_tt1;intret=pthread_create(&t1,NULL,test,NULL);if(ret!=0){printf("线程创建失败\n");}//避免直接退出sleep(2);printf("Mainrun..\n");}Output:#gcc-lpthreadtest_pytha.out&./aThreadID:31233ThreadID:31233Mainrun..(毫不犹豫退出)既然Python也是用C写的,为什么Pythonmulti-线程退出需要setDaemon吗???要解决这个问题,恐怕不能从主线程退出的那一刻开始。过去...Python解析器将在最后调用wait_for_thread_shutdown进行例行清理://python2.7/python/pythonrun.cstaticvoidwait_for_thread_shutdown(void){#ifdefWITH_THREADPyObject*result;PyThreadState*tstate=PyThreadState_GET();PyObject*threading=PyMapping_GetItemString(tstate->interp->modules,“穿线”);if(threading==NULL){/*线程未导入*/PyErr_Clear();返回;}result=PyObject_CallMethod(threading,"_shutdown","");如果(结果==NULL)PyErr_WriteUnraisable(线程);否则Py_DECREF(结果);Py_DECREF(threading);#endif}当我们看到#ifdefWITH_THREAD时,我们大概猜到它是不是多线程的,这个函数运行的逻辑不同。很明显,我们上面的脚本是Hit线程逻辑,所以会动态导入threading模块,然后执行_shutdown函数这个函数的内容,我们可以从threading模块中看到:#/usr/lib/python2.7/threading.py_shutdown=_MainThread()._exitfuncclass_MainThread(Thread):def__init__(self):Thread.__init__(self,name="MainThread")self._Thread__started.set()self._set_ident()with_active_limbo_lock:_active[_get_ident()]=selfdef_set_daemon(self):returnFalsedef_exitfunc(self):self._Thread__stop()t=_pickSomeNonDaemonThread()ift:if__debug__:self._note("%s:waitingforotherthreads",self)whilet:t.join()t=_pickSomeNonDaemonThread()if__debug__:self._note("%s:退出",self)self._Thread__delete()def_pickSomeNonDaemonThread():fortinenumerate():ifnott.daemonandt.is_alive():returntreturnNone_shutdown其实就是_MainThread()._exitfunc的内容,主要是它就是回收所有enumerate()返回的结果,所有join()和enumerate()是什么?这是我们平时使用的,即当前进程所有符合条件的Python线程对象:>>>printthreading.enumerate()[<_MainThread(MainThread,started140691994822400)>]#/usr/lib/python2.7/threading.pydefenumerate():"""返回当前所有线程对象的列表。该列表包括守护线程、由current_thread()创建的虚拟线程对象和主线程。它不包括已终止的线程和尚未终止的线程已启动。"""with_active_limbo_lock:return_active.values()+_limbo.values()满足条件???你满足什么条件??别着急,我告诉你:从起源说生存条件。在Python的线程模型中,虽然有GIL干扰,但线程是真正的原生线程。Python只是多加了一层封装:t_bootstrap,然后这层封装执行真正的处理函数在线程模块中,我们也可以看到类似的:#/usr/lib/python2.7/threading.pyclassThread(_Verbose):defstart(self):...omitwith_active_limbo_lock:_limbo[self]=self#focustry:_start_new_thread(self.__bootstrap,())exceptException:with_active_limbo_lock:del_limbo[self]#focusraiseself.__started.wait()def__bootstrap(self):try:self.__bootstrap_inner()except:ifself.__daemonicand_sysisNone:returnraisedef__bootstrap_inner(self):try:...omitwith_active_limbo_lock:_active[self.__ident]=self#keypointdel_limbo[self]#keypoint...省略上面的In一系列的代码,_limbo和_active的变化都标出了重点,我们可以得到如下定义:那么回到上面,在执行_MainThread()._exitfunc的时候,会检查整个流程中是否有_limbo+_active对象。只要有一个就会调用join(),这就是阻塞的原因。setDaemon用处无穷周期性阻塞还不行,聪明点帮用户杀线程也不是解决办法,那么如何做更优雅呢?即提供一种方式让用户设置随进程退出的flag,即setDaemon:classThread():...省略defsetDaemon(self,daemonic):self.daemon=daemonic...省略#In其实上面也贴过了,这里再贴一次def_pickSomeNonDaemonThread():fortinenumerate():ifnott.daemonandt.is_alive():returntreturnNone只要子线程都设置好了到setDaemon(True),然后主线程准备退出,都乖乖的被操作系统销毁回收了。之前一直很好奇,pthread没有daemon属性,为什么Python会有呢?结果这个东西真的只能在Python层起作用(手动笑脸)。在结语中,一个setDaemon可以引出很多对本质??内容的探索机会,比如线程的创建过程和管理过程。这些都是很有意思的内容,要大胆探索,不局限于使用~欢迎各位高手指出交流,QQ讨论群:258498217转载请注明出处:https://segmentfault.com/a/11...