前言
使用 Python 都不會(huì)錯(cuò)過線程這個(gè)知識(shí),但是每次談到線程,大家都下意識(shí)說 GIL 全局鎖,
但其實(shí)除了這個(gè)老生常談的話題,還有很多有價(jià)值的東西可以探索的,譬如: setDaemon() 。
線程的使用 與 存在的問題
我們會(huì)寫這樣的代碼來啟動(dòng)多線程:
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
...(兩個(gè)線程競相打印)
通過 Threading 我們可以很簡單的實(shí)現(xiàn)并發(fā)的需求,但是同時(shí)也給我們帶來了一個(gè)大難題: 怎么退出呢?
在上面的程序運(yùn)行中,我已經(jīng)嘗試按了多次的 ctrl-c ,都無法中斷這程序工作的熱情!最后是迫不得已用 kill 才結(jié)束。
那么怎樣才能可以避免這種問題呢?或者說,怎樣才能在主線程退出的時(shí)候,子線程也自動(dòng)退出呢?
守護(hù)線程
有過相似經(jīng)驗(yàn)的老司機(jī)肯定就知道, setDaemon() 將線程搞成 守護(hù)線程 不就得了唄:
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
(直接退出了)
直接退出?理所當(dāng)然,因?yàn)橹骶€程已經(jīng)執(zhí)行完了,確實(shí)是已經(jīng)結(jié)束了,正因?yàn)樵O(shè)置了守護(hù)線程,所以這時(shí)候子線程也一并退出了。
突如其來的 daemon
那么問題來了,我們以前學(xué) C 語言的時(shí)候,好像不用 Daemon 也可以啊,比如這個(gè):
#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 ???
想要解決這個(gè)問題,我們怕不是要從主線程退出的一刻開始講起,從前....
反藤摸瓜
Python 解析器在結(jié)束的時(shí)候,會(huì)調(diào)用 wait_for_thread_shutdown 來做個(gè)例行清理:
// 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 就大概猜到對(duì)于是否多線程,這個(gè)函數(shù)是運(yùn)行了不同的邏輯的
很明顯,我們上面的腳本,就是命中了這個(gè)線程邏輯,所以它會(huì)動(dòng)態(tài) import threading 模塊 ,然后執(zhí)行 _shutdown 函數(shù)。
這個(gè)函數(shù)的內(nèi)容,我們可以從 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
實(shí)際上也就是
_MainThread()._exitfunc
的內(nèi)容,主要是將
enumerate()
返回的所有結(jié)果,全部
join()
回收
而 enumerate() 是什么?
這個(gè)平時(shí)我們也會(huì)使用,就是當(dāng)前進(jìn)程的所有 符合條件 的 Python線程對(duì)象:
>>> print threading.enumerate()
[<_MainThread(MainThread, started 140691994822400)>]
# /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 的干涉,但是線程卻是實(shí)實(shí)在在的原生線程
Python 只是多加一層封裝: t_bootstrap ,然后再在這層封裝里面執(zhí)行真正的處理函數(shù)。
在 threading 模塊內(nèi),我們也能看到一個(gè)相似的:
# /usr/lib/python2.7/threading.py
class Thread(_Verbose):
def start(self):
...省略
with _active_limbo_lock:
_limbo[self] = self # 重點(diǎn)
try:
_start_new_thread(self.__bootstrap, ())
except Exception:
with _active_limbo_lock:
del _limbo[self] # 重點(diǎn)
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 # 重點(diǎn)
del _limbo[self] # 重點(diǎn)
...省略
在上面的一連串代碼中, _limbo 和 _active 的變化都已經(jīng)標(biāo)記了重點(diǎn),我們可以得到下面的定義:
_limbo : 就是調(diào)用了 start,但是還沒來得及 _start_new_thread 的對(duì)象
_active: 活生生的線程對(duì)象
那么回到上文,當(dāng) _MainThread()._exitfunc 執(zhí)行時(shí),是會(huì)檢查整個(gè)進(jìn)程是否存在 _limbo + _active 的對(duì)象,
只要存在一個(gè),就會(huì)調(diào)用 join() , 這個(gè)也就是堵塞的原因。
setDaemon 用處
無限期堵塞不行,自作聰明幫用戶強(qiáng)殺線程也不是辦法,那么怎么做才會(huì)比較優(yōu)雅呢?
那就是提供一個(gè)途徑,讓用戶來設(shè)置隨進(jìn)程退出的標(biāo)記,那就是 setDaemon :
class Thread():
...省略
def setDaemon(self, daemonic):
self.daemon = daemonic
...省略
# 其實(shí)上面也貼了,這里再貼一次
def _pickSomeNonDaemonThread():
for t in enumerate():
if not t.daemon and t.is_alive():
return t
return None
只要子線程,全部設(shè)置 setDaemon(True) , 那么主線程一準(zhǔn)備退出,全都乖乖地由操作系統(tǒng)銷毀回收。
之前一直很好奇,pthread 都沒有 daemon 屬性,為什么 Python 會(huì)有呢?
結(jié)果這玩意就是真的是僅作用于 Python 層(手動(dòng)笑臉)
結(jié)語
區(qū)區(qū)一個(gè) setDaemon 可以引出很多本質(zhì)內(nèi)容的探索機(jī)會(huì),比如線程的創(chuàng)建過程,管理流程等。
這些都是很有意思的內(nèi)容,我們應(yīng)該大膽探索,不局限于使用~
歡迎各位大神指點(diǎn)交流, QQ討論群: 258498217
轉(zhuǎn)載請(qǐng)注明來源: https://segmentfault.com/a/11...
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
