標籤:name als 順序 內容 也會 一起 異常 nic 注釋
本文以多個例子介紹Python多線程中daemon屬性值的區別。
回顧:
前面的文章簡單介紹了在現代作業系統中,每一個進程都認為自己獨佔所有的電腦資源。
或者說線程就是獨立的王國,進程間是相對獨立的,不可以隨便的共用資料。
線程就是省份,同一個進程內的線程可以共用進程的資源,每一個線程擁有自己的堆棧。
- 每個進程至少要有一個線程,並最為程式的入口,這個進程就是主線程。
- 每個進程至少要有一個主線程,其它線程稱為背景工作執行緒。
- 父線程:如果線程A啟動了一個線程B,A就是B的父線程。
- 子線程:B就是A的子線程
Python中,在構造線程對象時,可以設定daemon屬性,這個屬性必須在start方法前設定好。
主線程是程式啟動的第一個線程,主線程可以再啟動 n 個子線程。
daemon屬性可以不設定,預設為None,主線程預設是False。
看一段daemon屬性在源碼中是如何設計的:
class Thread: def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):#daemon屬性值預設是None if daemon is not None: self._daemonic = daemon else: self._daemonic = current_thread().daemon
在看完下面的幾個例子之後,就會理解源碼中的意思了。
daemon屬性值分為以下三種:
1) daemon=False
當daemon為False時,父線程在運行完畢後,會等待所有子線程退出才結束程式。
舉例:
import threadingimport timedef foo(): for i in range(3): print(‘i={},foo thread daemon is {}‘.format(i,threading.current_thread().isDaemon())) time.sleep(1)t = threading.Thread(target=foo,daemon=False)t.start()print("Main thread daemon is {}".format(threading.current_thread().isDaemon()))print("Main Thread Exit.")運行結果:i=0,foo thread daemon is FalseMain thread daemon is FalseMain Thread Exit.i=1,foo thread daemon is Falsei=2,foo thread daemon is False
通過 isDaemon() 方法可以返回當前線程的daemon值,主線程預設是False,子線程也是False的原因是建立線程對象時指定了daemon=False。
根據運行結果的順序可以得知,主程式線上程完線程對象後就立即啟動了,然後子線程返回了結果中第一行內容,然後sleep 1秒類比 IO,這時CPU發現子線程阻塞了,就立即切到主線程繼續執行,主線程先後列印第二行和第三行,此時主線程的代碼已經執行到結尾。然後,因為主線程為子線程設定了daemon=False屬性,這時就又發生了 線程切換到子線程,子線程先後執行完第四行和第五行,然後子線程就完全執行完畢,主線程看到子線程退出以後,也立即退出,整個程式結束。
2) daemon=True
當daemon為True時,父線程在運行完畢後,子線程無論是否正在運行,都會伴隨主線程一起退出。
舉例:
import threadingimport timedef foo(): for i in range(3): print(‘i={},foo thread daemon is {}‘.format(i,threading.current_thread().isDaemon())) time.sleep(1)t = threading.Thread(target=foo,daemon=True)t.start()print("Main thread daemon is {}".format(threading.current_thread().isDaemon()))print("Main Thread Exit.")運行結果 :i=0,foo thread daemon is TrueMain thread daemon is FalseMain Thread Exit.
從運行結果來看,當子線程設定daemon屬性為True時,即主線程不關心子線程運行狀態,主線程退出,子線程也必須跟著退出。
所以運行結果中子線程只執行了一句語句,就輪到主線程,主線程執行完最後兩句,就立即退出,整個程式結束。
3) 不設定,或者daemon=None
daemon屬性可以不設定,預設值是None。
舉例:
import threadingimport timedef bar(): while True: # 無限迴圈的子子線程 print(‘【bar】 daemon is {}‘.format(threading.current_thread().isDaemon())) time.sleep(1)def foo(): for i in range(3): #啟動3個子線程 print(‘i={},【foo】 thread daemon is {}‘.format(i,threading.current_thread().isDaemon())) t1 = threading.Thread(target=bar,daemon=None) t1.start()t = threading.Thread(target=foo,daemon=True)t.start()print("Main thread daemon is {}".format(threading.current_thread().isDaemon()))time.sleep(2)print("Main Thread Exit.")運行結果:i=0,【foo】 thread daemon is TrueMain thread daemon is False【bar】 daemon is Truei=1,【foo】 thread daemon is True【bar】 daemon is Truei=2,【foo】 thread daemon is True【bar】 daemon is True【bar】 daemon is True【bar】 daemon is True【bar】 daemon is TrueMain Thread Exit.
這裡在主線程中使用了延遲2秒,來讓子線程啟動的子線程有機會輸出其daemon屬性值,如果不設定延遲,因為子線程設定了daemon=Ture,子子線程daemon為None就相當於取的是父線程的daemon值(子子線程的父線程也就是子線程,子線程daemon=true),所以最終子子線程中的while無限迴圈還是被它的父線程(子線程)強制退出了。
再分別看下子子線程的daemon為False的情況:
import threadingimport timedef bar(): while True: # 無限迴圈的子子線程 print(‘【bar】 daemon is {}‘.format(threading.current_thread().isDaemon())) time.sleep(1)def foo(): for i in range(3): #啟動3個子線程 print(‘i={},【foo】 thread daemon is {}‘.format(i,threading.current_thread().isDaemon())) t1 = threading.Thread(target=bar,daemon=False) t1.start()t = threading.Thread(target=foo,daemon=True)t.start()print("Main thread daemon is {}".format(threading.current_thread().isDaemon()))time.sleep(2)print("Main Thread Exit.")運行結果:i=0,【foo】 thread daemon is TrueMain thread daemon is False【bar】 daemon is Falsei=1,【foo】 thread daemon is True【bar】 daemon is Falsei=2,【foo】 thread daemon is True【bar】 daemon is False【bar】 daemon is False【bar】 daemon is False【bar】 daemon is FalseMain Thread Exit.【bar】 daemon is False【bar】 daemon is False【bar】 daemon is False【bar】 daemon is False【bar】 daemon is False【bar】 daemon is False【bar】 daemon is False【bar】 daemon is False【bar】 daemon is False【bar】 daemon is False【bar】 daemon is False.......無限迴圈....
主線程本來是不等子線程執行完畢的,但子線程要等待子子線程執行完畢,子子線程又是無限迴圈。所以最終主線程也攔不住子子線程一直瘋狂的輸出,這就好比爺爺管得了兒子,但管不了孫子呀。
上面這個例子最後第二行的sleep(2)是在主線程中啟動並執行,如果注釋掉這條語句,就會發現運行結果是這樣的:
i=0,【foo】 thread daemon is TrueMain thread daemon is FalseMain Thread Exit.
子線程雖然運行了,但還沒來得及啟動子子線程,主線程就執行到最後了,直接結束掉了程式。
如果那怕讓子子線程啟動起來一個,就主線程就沒轍了,這傢伙瘋狂的輸出。
所以如果沒有sleep(2)這條語句,就看不到真正的效果了。
總結:
現在再來看daemon屬性在Thread類中的源碼,就可以理解了。
class Thread: def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):#daemon屬性值預設是None if daemon is not None: self._daemonic = daemon else: self._daemonic = current_thread().daemon
大致邏輯如下:
建立線程對象時傳入daemon屬性值
如果值不是None,也就是說傳入的是True或者False,當然這是假設,萬一有變態傳入亂七八糟的值呢,不過解譯器在運行時肯定會拋異常。
傳入的值是就作為該目標線程的daemon值。
如果沒有傳入值,或傳入的是daemon=None,就等同於None,該目標線程的值就取父線程的daemon值作為自己的daemon的值。
也就是要分清楚源碼中的current_thread()是哪個線程,在第三個例子中,是在子線程中建立子子線程對象,所以current_thread()這個當前線程就是子線程,子子線程沒有傳入daemon屬性值,所以建立時就把子線程的daemon屬性值作為該子子線程的daemon屬性值。
考慮這樣的情境,主線程只啟動了子線程,子線程麼有子子線程,子線程沒有設定daemon屬性時,那就是誰建立這個線程(當然是主線程建立的),就把它的daemon屬性值作為這個線程的daemon值。主線程預設的daemon值是False,所以這個子線程最終也會傳入的是False。
所以:
import threadingimport timedef foo(): for i in range(3): print(‘i={},【foo】 thread daemon is {}‘.format(i,threading.current_thread().isDaemon())) time.sleep(1)t = threading.Thread(target=foo)t.start()print("Main thread daemon is {}".format(threading.current_thread().isDaemon()))# time.sleep(2)print("Main Thread Exit.")運行結果:i=0,【foo】 thread daemon is FalseMain thread daemon is FalseMain Thread Exit.i=1,【foo】 thread daemon is Falsei=2,【foo】 thread daemon is False
子線程 daemon=False。
.
[Python 多線程] 詳解daemon屬性值None,False,True的區別 (五)