摘要:而線程則是每秒通過輸出當(dāng)前進程內(nèi)所有活躍的線程。如果使用強制手段干掉線程,那么很大幾率出現(xiàn)意想不到的。只是通過來約束這些線程,來決定什么時候開始調(diào)度,比方說運行了多少個指令就交出,至于誰奪得花魁,得聽操作系統(tǒng)的。
背景
開工前我就覺得有什么不太對勁,感覺要背鍋。這可不,上班第三天就捅鍋了。
我們有個了不起的后臺程序,可以動態(tài)加載模塊,并以線程方式運行,通過這種形式實現(xiàn)插件的功能。而模塊更新時候,后臺程序自身不會退出,只會將模塊對應(yīng)的線程關(guān)閉、更新代碼再啟動,6 得不行。
于是乎我就寫了個模塊準(zhǔn)備大展身手,結(jié)果忘記寫退出函數(shù)了,導(dǎo)致每次更新模塊都新創(chuàng)建一個線程,除非重啟那個程序,否則那些線程就一直茍活著。
這可不行啊,得想個辦法清理呀,要不然怕是要炸了。
那么怎么清理呢?我能想到的就是兩步走:
找出需要清理的線程號 tid;
銷毀它們;
找出線程ID和平時的故障排查相似,先通過 ps 命令看看目標(biāo)進程的線程情況,因為已經(jīng)是 setName 設(shè)置過線程名,所以正常來說應(yīng)該是看到對應(yīng)的線程的。 直接用下面代碼來模擬這個線程:
Python 版本的多線程
#coding: utf8 import threading import os import time def tt(): info = threading.currentThread() while True: print "pid: ", os.getpid() print info.name, info.ident time.sleep(3) t1 = threading.Thread(target=tt) t1.setName("OOOOOPPPPP") t1.setDaemon(True) t1.start() t2 = threading.Thread(target=tt) t2.setName("EEEEEEEEE") t2.setDaemon(True) t2.start() t1.join() t2.join()
輸出:
root@10-46-33-56:~# python t.py pid: 5613 OOOOOPPPPP 139693508122368 pid: 5613 EEEEEEEEE 139693497632512 ...
可以看到在 Python 里面輸出的線程名就是我們設(shè)置的那樣,然而 Ps 的結(jié)果卻是令我懷疑人生:
root@10-46-33-56:~# ps -Tp 5613 PID SPID TTY TIME CMD 5613 5613 pts/2 00:00:00 python 5613 5614 pts/2 00:00:00 python 5613 5615 pts/2 00:00:00 python
正常來說不該是這樣呀,我有點迷了,難道我一直都是記錯了?用別的語言版本的多線程來測試下:
C 版本的多線程
#include#include #include #include void *test(void *name) { pid_t pid, tid; pid = getpid(); tid = syscall(__NR_gettid); char *tname = (char *)name; // 設(shè)置線程名字 prctl(PR_SET_NAME, tname); while(1) { printf("pid: %d, thread_id: %u, t_name: %s ", pid, tid, tname); sleep(3); } } int main() { pthread_t t1, t2; void *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:~# gcc t.c -lpthread && ./a.out pid: 5575, thread_id: 5577, t_name: Love_test_2 pid: 5575, thread_id: 5576, t_name: Love_test_1 pid: 5575, thread_id: 5577, t_name: Love_test_2 pid: 5575, thread_id: 5576, t_name: Love_test_1 ...
用 PS 命令再次驗證:
root@10-46-33-56:~# ps -Tp 5575 PID SPID TTY TIME CMD 5575 5575 pts/2 00:00:00 a.out 5575 5576 pts/2 00:00:00 Love_test_1 5575 5577 pts/2 00:00:00 Love_test_2
這個才是正確嘛,線程名確實是可以通過 Ps 看出來的嘛!
不過為啥 Python 那個看不到呢?既然是通過 setName 設(shè)置線程名的,那就看看定義咯:
[threading.py] class Thread(_Verbose): ... @property def name(self): """A string used for identification purposes only. It has no semantics. Multiple threads may be given the same name. The initial name is set by the constructor. """ assert self.__initialized, "Thread.__init__() not called" return self.__name @name.setter def name(self, name): assert self.__initialized, "Thread.__init__() not called" self.__name = str(name) def setName(self, name): self.name = name ...
看到這里其實只是在 Thread 對象的屬性設(shè)置了而已,并沒有動到根本,那肯定就是看不到咯~
這樣看起來,我們已經(jīng)沒辦法通過 ps 或者 /proc/ 這類手段在外部搜索 python 線程名了,所以我們只能在 Python 內(nèi)部來解決。
于是問題就變成了,怎樣在 Python 內(nèi)部拿到所有正在運行的線程呢?
threading.enumerate 可以完美解決這個問題!Why?
Because 在下面這個函數(shù)的 doc 里面說得很清楚了,返回所有活躍的線程對象,不包括終止和未啟動的。
[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()
因為拿到的是 Thread 的對象,所以我們通過這個能到該線程相關(guān)的信息!
請看完整代碼示例:
#coding: utf8 import threading import os import time def get_thread(): pid = os.getpid() while True: ts = threading.enumerate() print "------- Running threads On Pid: %d -------" % pid for t in ts: print t.name, t.ident print time.sleep(1) def tt(): info = threading.currentThread() pid = os.getpid() while True: print "pid: {}, tid: {}, tname: {}".format(pid, info.name, info.ident) time.sleep(3) return t1 = 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:~# python t_show.py pid: 6258, tid: Thread-test1, tname: 139907597162240 pid: 6258, tid: Thread-test2, tname: 139907586672384 ------- Running threads On Pid: 6258 ------- MainThread 139907616806656 Thread-test1 139907597162240 Checker 139907576182528 Thread-test2 139907586672384 ------- Running threads On Pid: 6258 ------- MainThread 139907616806656 Thread-test1 139907597162240 Checker 139907576182528 Thread-test2 139907586672384 ------- Running threads On Pid: 6258 ------- MainThread 139907616806656 Thread-test1 139907597162240 Checker 139907576182528 Thread-test2 139907586672384 ------- Running threads On Pid: 6258 ------- MainThread 139907616806656 Checker 139907576182528 ...
代碼看起來有點長,但是邏輯相當(dāng)簡單,Thread-test1 和 Thread-test2 都是打印出當(dāng)前的 pid、線程 id 和 線程名字,然后 3s 后退出,這個是想模擬線程正常退出。
而 Checker 線程則是每秒通過 threading.enumerate 輸出當(dāng)前進程內(nèi)所有活躍的線程。
可以明顯看到一開始是可以看到 Thread-test1 和 Thread-test2的信息,當(dāng)它倆退出之后就只剩下 MainThread 和 Checker 自身而已了。
銷毀指定線程既然能拿到名字和線程 id,那我們也就能干掉指定的線程了!
假設(shè)現(xiàn)在 Thread-test2 已經(jīng)黑化,發(fā)瘋了,我們需要制止它,那我們就可以通過這種方式解決了:
在上面的代碼基礎(chǔ)上,增加和補上下列代碼:
def _async_raise(tid, exctype): """raises the exception, performs cleanup if needed""" tid = ctypes.c_long(tid) if not inspect.isclass(exctype): exctype = type(exctype) res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype)) if res == 0: raise ValueError("invalid thread id") elif res != 1: ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None) raise SystemError("PyThreadState_SetAsyncExc failed") def stop_thread(thread): _async_raise(thread.ident, SystemExit) def get_thread(): pid = os.getpid() while True: ts = threading.enumerate() print "------- Running threads On Pid: %d -------" % pid for t in ts: print t.name, t.ident, t.is_alive() if t.name == "Thread-test2": print "I am go dying! Please take care of yourself and drink more hot water!" stop_thread(t) print time.sleep(1)
輸出
root@10-46-33-56:~# python t_show.py pid: 6362, tid: 139901682108160, tname: Thread-test1 pid: 6362, tid: 139901671618304, tname: Thread-test2 ------- Running threads On Pid: 6362 ------- MainThread 139901706389248 True Thread-test1 139901682108160 True Checker 139901661128448 True Thread-test2 139901671618304 True Thread-test2: I am go dying. Please take care of yourself and drink more hot water! ------- Running threads On Pid: 6362 ------- MainThread 139901706389248 True Thread-test1 139901682108160 True Checker 139901661128448 True Thread-test2 139901671618304 True Thread-test2: I am go dying. Please take care of yourself and drink more hot water! pid: 6362, tid: 139901682108160, tname: Thread-test1 ------- Running threads On Pid: 6362 ------- MainThread 139901706389248 True Thread-test1 139901682108160 True Checker 139901661128448 True // Thread-test2 已經(jīng)不在了
一頓操作下來,雖然我們這樣對待 Thread-test2,但它還是關(guān)心著我們:多喝熱水,
PS: 熱水雖好,八杯足矣,請勿貪杯哦。
書回正傳,上述的方法是極為粗暴的,為什么這么說呢?
因為它的原理是:利用 Python 內(nèi)置的 API,觸發(fā)指定線程的異常,讓其可以自動退出;
萬不得已真不要用這種方法,有一定概率觸發(fā)不可描述的問題。切記!別問我為什么會知道...
為什么停止線程這么難多線程本身設(shè)計就是在進程下的協(xié)作并發(fā),是調(diào)度的最小單元,線程間分食著進程的資源,所以會有許多鎖機制和狀態(tài)控制。
如果使用強制手段干掉線程,那么很大幾率出現(xiàn)意想不到的bug。 而且最重要的鎖資源釋放可能也會出現(xiàn)意想不到問題。
我們甚至也無法通過信號殺死進程那樣直接殺線程,因為 kill 只有對付進程才能達(dá)到我們的預(yù)期,而對付線程明顯不可以,不管殺哪個線程,整個進程都會退出!
而因為有 GIL,使得很多童鞋都覺得 Python 的線程是Python 自行實現(xiàn)出來的,并非實際存在,Python 應(yīng)該可以直接銷毀吧?
然而事實上 Python 的線程都是貨真價實的線程!
什么意思呢?Python 的線程是操作系統(tǒng)通過 pthread 創(chuàng)建的原生線程。Python 只是通過 GIL 來約束這些線程,來決定什么時候開始調(diào)度,比方說運行了多少個指令就交出 GIL,至于誰奪得花魁,得聽操作系統(tǒng)的。
如果是單純的線程,其實系統(tǒng)是有辦法終止的,比如: pthread_exit,pthread_kill 或 pthread_cancel, 詳情可看:https://www.cnblogs.com/Creat...
很可惜的是: Python 層面并沒有這些方法的封裝!我的天,好氣!可能人家覺得,線程就該溫柔對待吧。
如何溫柔退出線程想要溫柔退出線程,其實差不多就是一句廢話了~
要么運行完退出,要么設(shè)置標(biāo)志位,時常檢查標(biāo)記位,該退出的就退出咯。
擴展《如何正確的終止正在運行的子線程》:https://www.cnblogs.com/Creat...
《不要粗暴的銷毀python線程》:http://xiaorui.cc/2017/02/22/...
歡迎各位大神指點交流, QQ討論群: 258498217
轉(zhuǎn)載請注明來源: https://segmentfault.com/a/11...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/43192.html
摘要:但現(xiàn)在線程沒有優(yōu)先級,沒有線程組,不能被銷毀停止暫停開始和打斷。守護線程也會結(jié)束,并強行終止整個程序。在中,他是目前可用的最底層的同步原語,由模塊提供。當(dāng)處于狀態(tài)時,方法可以將狀態(tài)變?yōu)椋⒘⒓捶祷亍7駝t會拋出錯誤。對象實現(xiàn)某些服務(wù)的共進退。 Python的threading模塊松散地基于Java的threading模塊。但現(xiàn)在線程沒有優(yōu)先級,沒有線程組,不能被銷毀、停止、暫停、開始和打...
閱讀 1098·2021-11-15 18:00
閱讀 2813·2021-09-22 15:18
閱讀 1974·2021-09-04 16:45
閱讀 756·2019-08-30 15:55
閱讀 3867·2019-08-30 13:10
閱讀 1343·2019-08-30 11:06
閱讀 1992·2019-08-29 12:51
閱讀 2300·2019-08-26 13:55