[Python 多线程] 详解daemon属性值None,False,True的区别 (五)
本文以多個例子介紹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 threading
import time
def 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 False
Main thread daemon is False
Main Thread Exit.
i=1,foo thread daemon is False
i=2,foo thread daemon is False
通過 isDaemon() 方法可以返回當前線程的daemon值,主線程默認是False,子線程也是False的原因是創建線程對象時指定了daemon=False。
根據運行結果的順序可以得知,主程序在線程完線程對象后就立即啟動了,然后子線程返回了結果中第一行內容,然后sleep 1秒模擬 IO,這時CPU發現子線程阻塞了,就立即切到主線程繼續執行,主線程先后打印第二行和第三行,此時主線程的代碼已經執行到結尾。然后,因為主線程為子線程設置了daemon=False屬性,這時就又發生了 線程切換到子線程,子線程先后執行完第四行和第五行,然后子線程就完全執行完畢,主線程看到子線程退出以后,也立即退出,整個程序結束。
2) daemon=True
當daemon為True時,父線程在運行完畢后,子線程無論是否正在運行,都會伴隨主線程一起退出。
舉例:
import threading
import time
def 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 True
Main thread daemon is False
Main Thread Exit.
從運行結果來看,當子線程設置daemon屬性為True時,即主線程不關心子線程運行狀態,主線程退出,子線程也必須跟著退出。
所以運行結果中子線程只執行了一句語句,就輪到主線程,主線程執行完最后兩句,就立即退出,整個程序結束。
3) 不設置,或者daemon=None
daemon屬性可以不設置,默認值是None。
舉例:
import threading
import time
def 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 True
Main thread daemon is False
【bar】 daemon is True
i=1,【foo】 thread daemon is True
【bar】 daemon is True
i=2,【foo】 thread daemon is True
【bar】 daemon is True
【bar】 daemon is True
【bar】 daemon is True
【bar】 daemon is True
Main Thread Exit.
這里在主線程中使用了延遲2秒,來讓子線程啟動的子線程有機會輸出其daemon屬性值,如果不設置延遲,因為子線程設置了daemon=Ture,子子線程daemon為None就相當于取的是父線程的daemon值(子子線程的父線程也就是子線程,子線程daemon=true),所以最終子子線程中的while無限循環還是被它的父線程(子線程)強制退出了。
再分別看下子子線程的daemon為False的情況:
import threading
import time
def 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 True
Main thread daemon is False
【bar】 daemon is False
i=1,【foo】 thread daemon is True
【bar】 daemon is False
i=2,【foo】 thread daemon is True
【bar】 daemon is False
【bar】 daemon is False
【bar】 daemon is False
【bar】 daemon is False
Main 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 True Main thread daemon is False Main 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 threading
import time
def 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 False
Main thread daemon is False
Main Thread Exit.
i=1,【foo】 thread daemon is False
i=2,【foo】 thread daemon is False
子線程 daemon=False。
.
總結
以上是生活随笔為你收集整理的[Python 多线程] 详解daemon属性值None,False,True的区别 (五)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: svn 红叉叉图标解决方法
- 下一篇: 使用PHP开发HR系统(5)