当前位置: 首页 > Linux

Python:线程定位与销毁

时间:2023-04-06 19:38:06 Linux

背景开始工作之前,感觉哪里不对,感觉要背锅了。这不,上班第三天,我就爆锅了。我们有一个了不起的后台程序,可以动态加载模块并以线程的方式运行,并以这种形式实现插件的功能。当模块更新时,后台程序本身不会退出,只会关闭模块对应的线程,更新代码再启动,6是不可能的。于是我写了一个模块来一展身手,但是忘记写exit函数,导致每次更新模块都会创建一个新的线程,除非程序重启,否则这些线程会一直存活。这不行,得想办法收拾一下,不然怕炸了。那么如何清洗呢?我能想到的就是两步:找出需要清理的线程号tid;摧毁他们;查找线程ID和平时排查类似,先用ps命令查看目标进程的线程状态,因为已经通过setName设置了线程名,所以正常应该可以看到对应的线程。直接用下面的代码模拟这个线程:Python版多线程#coding:utf8importthreadingimportosimporttimedeftt():info=threading.currentThread()whileTrue:print'pid:',os.getpid()printinfo.name,info.identtime.sleep(3)t1=threading.Thread(target=tt)t1.setName('OOOOOPPPPP')t1.setDaemon(True)t1.start()t2=threading.Thread(target=tt)t2.setName('EEEEEEEEEE')t2.setDaemon(True)t2.start()t1.join()t2.join()输出:root@10-46-33-56:~#pythont.pypid:5613OOOOOPPPPP139693508122368pid:5613EEEEEEEEEE139693497632512...可以看到Python中输出的线程名正是我们设置的,但是ps的结果让我怀疑人生:root@10-46-33-56:~#ps-Tp5613PIDSPIDTTYTIMECMD56135613pts/200:00:00python56135614pts/200:00:00python56135615pts/200:00:00Python平时应该不会这样吧,我有点迷茫,是不是一直记错了吗?使用其他语言版本的多线程测试:C版多线程#include#include#include#includevoid*test(void*name){pid_tpid,tid;pid=getpid();tid=系统调用(__NR_gettid);char*tname=(char*)name;//设置线程名称prctl(PR_SET_NAME,tname);while(1){printf("pid:%d,thread_id:%u,t_name:%s\n",pid,tid,tname);睡觉(3);}}intmain(){pthread_tt1,t2;作废*ret;pthread_create(&t1,NULL,test,(void*)"Love_test_1");pthread_create(&t2,NULL,test,(void*)"Love_test_2");pthread_join(t1,&ret);pthread_join(t2,&ret);}输出:root@10-46-33-56:~#gcct.c-lpthread&&./a.outpid:5575,thread_id:5577,t_name:Love_test_2pid:5575,thread_id:5576,t_name:Love_test_1pid:5575,thread_id:5577,t_name:Love_test_2pid:5575,thread_id_id:557..再次用PS命令验证:root@10-46-33-56:~#ps-Tp5575PIDSPIDTTYTIMECMD55755575pts/200:00:00a.out55755576pts/200:00:00Love_test_155755577pts/200:00:00Love_test_2这个是对的,线程名确实可以ps看出来!但是为什么Python看不到呢?既然通过了setName设置线程名称,然后看定义:[threading.py]classThread(_Verbose):...@propertydefname(self):"""一个字符串,仅用于标识目的。它没有语义。多个线程可以同名。初始名称由构造函数设置。"""assertself.__initialized,"Thread.__init__()notcalled"returnself.__name@name.setterdefname(self,name):assertself.__initialized,"Thread.__init__()notcalled"self.__name=str(name)defsetName(self,name):self.name=name...看到这其实只是Thread对象的一个??属性刚刚设置的,并没有移动到根目录下,所以肯定是看不见的~看来我们没有办法通过ps或者/proc/在外部搜索python线程名,只能在Python内部搜索所以问题变成了,如何在Python中获取所有正在运行的线程?threading.enumerate可以完美解决这个问题!为什么?因为在下面函数的文档中明确说明,返回所有活动的线程对象,不包括终止的和未启动的。[threading.py]defenumerate():"""返回当前所有线程对象的列表。该列表包括守护线程、由current_thread()创建的虚拟线程对象和主线程。它不包括已终止的线程和线程"""with_active_limbo_lock:return_active.values()+_limbo.values()因为我们得到的是Thread对象,所以我们可以通过这个得到线程相关的信息!请查看完整代码示例:#coding:utf8importthreadingimportosimporttimedefget_thread():pid=os.getpid()whileTrue:ts=threading.enumerate()print'------RunningthreadsOnPid:%d-------'%pidfortints:printt.name,t.identprinttime.sleep(1)deftt():info=threading.currentThread()pid=os.getpid()whileTrue:print'pid:{},tid:{},tname:{}'.format(pid,info.name,info.ident)time.sleep(3)returnt1=threading.Thread(target=tt)t1.setName('Thread-test1')t1.setDaemon(True)t1.start()t2=threading.Thread(target=tt)t2.setName('Thread-test2')t2.setDaemon(True)t2.start()t3=threading.Thread(target=get_thread)t3.setName('Checker')t3.setDaemon(True)t3.start()t1.join()t2.join()t3.join()输出:root@10-46-33-56:~#pythont_show.pypid:6258,tid:Thread-test1,tname:139907597162240pid:6258,tid:Thread-test2,tname:139907586672384------RunningthreadsOnPid:6258------MainThread139907616806656Thread-test1139907597162240Checker139907576182528Thread-test2139907586672384-------RunningthreadsOnPid:6258-------MainThread139907616806656Thread-test1139907597162240Checker139907576182528Thread-test2139907586672384-------RunningthreadsOnPid:6258-------MainThread139907616806656Thread-test1139907597162240Checker139907576182528Thread-test2139907586672384-------RunningthreadsOnPid:6258-------MainThread139907616806656Checker139907576182528...代码看起来有点长,但是逻辑相当简单,Thread-test1和Thread-test2就是打印出当前pid,线程id和线程名,3s后退出。这是为了模拟线程正常退出,而Checker线程每秒通过threading.enumerate输出当前进程中所有活跃的线程。可以清楚的看到,一开始可以看到Thread-test1和Thread-test2的信息,退出时只剩下MainThread和Checker本身。销毁指定线程既然我们可以拿到名字和线程id,那么我们也可以杀掉指定线程!假设Thread-test2已经黑化发疯了,我们需要阻止它,那么我们可以这样解决:在上面代码的基础上,添加补充如下代码:def_async_raise(tid,exctype):"""引发异常,如果需要则执行清理"""tid=ctypes.c_long(tid)ifnotinspect.isclass(exctype):exctype=type(exctype)res=ctypes.pythonapi.PyThreadState_SetAsyncExc(tid,ctypes.py_object(exctype))如果res==0:raiseValueError("invalidthreadid")elifres!=1:ctypes.pythonapi.PyThreadState_SetAsyncExc(tid,None)raiseSystemError("PyThreadState_SetAsyncExcfailed")defstop_thread(thread):_async_raise(thread.ident,SystemExit)defget_thread():pid=os.getpid()whileTrue:ts=threading.enumerate()print'------RunningthreadsOnPid:%d-------'%pidfortints:printt.name,t.ident,t.is_alive()ift.name=='Thread-test2':print'我要死了!请多多关照自己多喝点热水!stop_thread(t)printtime.sleep(1)输出root@10-46-33-56:~#pythont_show.pypid:6362,tid:139901682108160,tname:Thread-test1pid:6362,tid:139901671618304,tname:Thread-test2------在Pid上运行线程:6362------MainThread139901706389248TrueThread-test1139901682108160TrueChecker139901661128448TrueThread-test2139901671618304TrueThread-test2:我快死了。请照顾好自己,多喝热水!------RunningthreadsOnPid:6362------MainThread139901706389248TrueThread-test1139901682108160TrueChecker139901661128448TrueThread-test2139901671618304TrueThread-test2:我是go快死了。请保重身体,多喝热水!pid:6362,tid:139901682108160,tname:Thread-test1-------RunningthreadsOnPid:6362------MainThread139901706389248TrueThread-test1139901682108160TrueChecker139901661128448True//Thread-test2已经不在了顿操作下来,虽然然我们这样对待Thread-test2,它还是关心我们:多喝热水,PS:热水虽然好,八杯就够了,请不要贪杯。言归正传,上面的方法极其粗暴,为什么要这么说呢?因为它的原理是:利用Python内置的API触发指定线程的异常,使其自动退出;万不得已不要使用这种方法,有一定的概率会引发不可描述的问题。记住!不要问我为什么我知道……为什么停止线程如此困难。多线程的设计本身就是进程下的协同并发。它是调度的最小单位。进程的资源是线程间共享的,所以会有很多锁机制和状态控制。如果线程被强行杀死,很可能会出现意想不到的错误。而最重要的锁资源的释放也可能出现意想不到的问题。我们甚至不能像通过信号杀死进程那样直接杀死线程,因为kill只能通过对进程的处理来达到我们的预期,而对线程的处理显然是不行的。无论哪个线程被杀死,整个进程都会退出!而且因为有了GIL,很多童鞋都认为Python线程是Python自己实现的,其实并不存在。Python应该直接销毁吧?事实上,Python线程才是真正的线程!这意味着什么?Python线程是操作系统通过pthreads创建的本机线程。Python只是通过GIL来约束这些线程来决定什么时候开始调度。比如GIL运行多少条指令就交出来。至于谁赢得花魁,就看操作系统了。如果是简单的线程,系统其实是有办法终止它的,比如:pthread_exit、pthread_kill或者pthread_cancel,具体可以参考:https://www.cnblogs.com/Creat...可惜了那:Python级别没有这些Method的封装!我的天,好生气!也许人们认为线程应该被温柔对待。如何轻轻地退出线程如果要轻轻地退出线程,那几乎是一句废话~要么运行完就退出,要么设置flag,经常检查flag,该退出就退出。扩充《如何正确的终止正在运行的子线程》:https://www.cnblogs.com/Creat...《不要粗暴的销毁python线程》:http://xiaorui.cc/2017/02/22/...欢迎各位大神指教交流,QQ讨论群:258498217转载请注明出处:https://segmentfault.com/a/11...