亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

Python:線程為什么搞個setDaemon

系統 1836 0

前言

使用 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(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 的干涉,但是線程卻是實實在在的原生線程

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
轉載請注明來源: https://segmentfault.com/a/11...


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 九九九九九热 | 国产成人a v在线影院 | 久久综合色播 | 亚洲欧美日韩国产精品 | 日本不卡一区二区三区 | 青青青国产免费手机视频在线观看 | 亚洲热在线视频 | 日本精品久久久久中文字幕 | 91亚洲精品| 久久国产一片免费观看 | 久久精品亚洲一区二区三区浴池 | 精品久久成人 | 在线看国产 | 日日操夜夜操狠狠操 | 欧美不卡在线观看 | 亚洲自拍成人 | 亚洲国产一区二区三区综合片 | 九九精品九九 | 日本一级特黄视频 | 亚洲一区二区三区四区 | 欧美午夜网站 | 久久久欧美综合久久久久 | 在线欧美一区 | 伦理亚洲| 亚洲国产精品专区 | 国产综合社区 | 欧美日本高清 | 国产成人在线免费视频 | 爆操白虎| 久久婷婷久久一区二区三区 | 99九九国产精品免费视频 | 97国产成人精品免费视频 | 色播性播爱播放影院 | 久久久久九九精品影院 | 四虎影院在线网址 | 一区二区三区在线播放视频 | 四虎影院免费观看 | 拍拍拍精品视频在线观看 | 免费a级在线观看完整片 | 成人国产第一区在线观看 | 国产一精品一aⅴ一免费 |