[Python 多线程] 详解daemon属性值None,False,True的区别 (五)
本文以多個(gè)例子介紹Python多線程中daemon屬性值的區(qū)別。
回顧:
前面的文章簡(jiǎn)單介紹了在現(xiàn)代操作系統(tǒng)中,每一個(gè)進(jìn)程都認(rèn)為自己獨(dú)占所有的計(jì)算機(jī)資源。
或者說(shuō)線程就是獨(dú)立的王國(guó),進(jìn)程間是相對(duì)獨(dú)立的,不可以隨便的共享數(shù)據(jù)。
線程就是省份,同一個(gè)進(jìn)程內(nèi)的線程可以共享進(jìn)程的資源,每一個(gè)線程擁有自己的堆棧。
每個(gè)進(jìn)程至少要有一個(gè)線程,并最為程序的入口,這個(gè)進(jìn)程就是主線程。
每個(gè)進(jìn)程至少要有一個(gè)主線程,其它線程稱為工作線程。
父線程:如果線程A啟動(dòng)了一個(gè)線程B,A就是B的父線程。
子線程:B就是A的子線程
Python中,在構(gòu)造線程對(duì)象時(shí),可以設(shè)置daemon屬性,這個(gè)屬性必須在start方法前設(shè)置好。
主線程是程序啟動(dòng)的第一個(gè)線程,主線程可以再啟動(dòng) n 個(gè)子線程。
daemon屬性可以不設(shè)置,默認(rèn)為None,主線程默認(rèn)是False。
看一段daemon屬性在源碼中是如何設(shè)計(jì)的:
class Thread:
def __init__(self, group=None, target=None, name=None,
args=(), kwargs=None, *, daemon=None):#daemon屬性值默認(rèn)是None
if daemon is not None:
self._daemonic = daemon
else:
self._daemonic = current_thread().daemon
在看完下面的幾個(gè)例子之后,就會(huì)理解源碼中的意思了。
daemon屬性值分為以下三種:
1) daemon=False
當(dāng)daemon為False時(shí),父線程在運(yùn)行完畢后,會(huì)等待所有子線程退出才結(jié)束程序。
舉例:
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.")
運(yùn)行結(jié)果:
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
通過(guò) isDaemon() 方法可以返回當(dāng)前線程的daemon值,主線程默認(rèn)是False,子線程也是False的原因是創(chuàng)建線程對(duì)象時(shí)指定了daemon=False。
根據(jù)運(yùn)行結(jié)果的順序可以得知,主程序在線程完線程對(duì)象后就立即啟動(dòng)了,然后子線程返回了結(jié)果中第一行內(nèi)容,然后sleep 1秒模擬 IO,這時(shí)CPU發(fā)現(xiàn)子線程阻塞了,就立即切到主線程繼續(xù)執(zhí)行,主線程先后打印第二行和第三行,此時(shí)主線程的代碼已經(jīng)執(zhí)行到結(jié)尾。然后,因?yàn)橹骶€程為子線程設(shè)置了daemon=False屬性,這時(shí)就又發(fā)生了 線程切換到子線程,子線程先后執(zhí)行完第四行和第五行,然后子線程就完全執(zhí)行完畢,主線程看到子線程退出以后,也立即退出,整個(gè)程序結(jié)束。
2) daemon=True
當(dāng)daemon為T(mén)rue時(shí),父線程在運(yùn)行完畢后,子線程無(wú)論是否正在運(yùn)行,都會(huì)伴隨主線程一起退出。
舉例:
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.")
運(yùn)行結(jié)果 :
i=0,foo thread daemon is True
Main thread daemon is False
Main Thread Exit.
從運(yùn)行結(jié)果來(lái)看,當(dāng)子線程設(shè)置daemon屬性為T(mén)rue時(shí),即主線程不關(guān)心子線程運(yùn)行狀態(tài),主線程退出,子線程也必須跟著退出。
所以運(yùn)行結(jié)果中子線程只執(zhí)行了一句語(yǔ)句,就輪到主線程,主線程執(zhí)行完最后兩句,就立即退出,整個(gè)程序結(jié)束。
3) 不設(shè)置,或者daemon=None
daemon屬性可以不設(shè)置,默認(rèn)值是None。
舉例:
import threading
import time
def bar():
while True: # 無(wú)限循環(huán)的子子線程
print('【bar】 daemon is {}'.format(threading.current_thread().isDaemon()))
time.sleep(1)
def foo():
for i in range(3): #啟動(dòng)3個(gè)子線程
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.")
運(yùn)行結(jié)果:
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秒,來(lái)讓子線程啟動(dòng)的子線程有機(jī)會(huì)輸出其daemon屬性值,如果不設(shè)置延遲,因?yàn)樽泳€程設(shè)置了daemon=Ture,子子線程daemon為None就相當(dāng)于取的是父線程的daemon值(子子線程的父線程也就是子線程,子線程daemon=true),所以最終子子線程中的while無(wú)限循環(huán)還是被它的父線程(子線程)強(qiáng)制退出了。
再分別看下子子線程的daemon為False的情況:
import threading
import time
def bar():
while True: # 無(wú)限循環(huán)的子子線程
print('【bar】 daemon is {}'.format(threading.current_thread().isDaemon()))
time.sleep(1)
def foo():
for i in range(3): #啟動(dòng)3個(gè)子線程
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.")
運(yùn)行結(jié)果:
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
.......無(wú)限循環(huán)....
主線程本來(lái)是不等子線程執(zhí)行完畢的,但子線程要等待子子線程執(zhí)行完畢,子子線程又是無(wú)限循環(huán)。所以最終主線程也攔不住子子線程一直瘋狂的輸出,這就好比爺爺管得了兒子,但管不了孫子呀。
上面這個(gè)例子最后第二行的sleep(2)是在主線程中運(yùn)行的,如果注釋掉這條語(yǔ)句,就會(huì)發(fā)現(xiàn)運(yùn)行結(jié)果是這樣的:
i=0,【foo】 thread daemon is True Main thread daemon is False Main Thread Exit.
子線程雖然運(yùn)行了,但還沒(méi)來(lái)得及啟動(dòng)子子線程,主線程就執(zhí)行到最后了,直接結(jié)束掉了程序。
如果那怕讓子子線程啟動(dòng)起來(lái)一個(gè),就主線程就沒(méi)轍了,這家伙瘋狂的輸出。
所以如果沒(méi)有sleep(2)這條語(yǔ)句,就看不到真正的效果了。
總結(jié):
現(xiàn)在再來(lái)看daemon屬性在Thread類(lèi)中的源碼,就可以理解了。
class Thread:
def __init__(self, group=None, target=None, name=None,
args=(), kwargs=None, *, daemon=None):#daemon屬性值默認(rèn)是None
if daemon is not None:
self._daemonic = daemon
else:
self._daemonic = current_thread().daemon
大致邏輯如下:
創(chuàng)建線程對(duì)象時(shí)傳入daemon屬性值
如果值不是None,也就是說(shuō)傳入的是True或者False,當(dāng)然這是假設(shè),萬(wàn)一有變態(tài)傳入亂七八糟的值呢,不過(guò)解釋器在運(yùn)行時(shí)肯定會(huì)拋異常。
傳入的值是就作為該目標(biāo)線程的daemon值。
如果沒(méi)有傳入值,或傳入的是daemon=None,就等同于None,該目標(biāo)線程的值就取父線程的daemon值作為自己的daemon的值。
也就是要分清楚源碼中的current_thread()是哪個(gè)線程,在第三個(gè)例子中,是在子線程中創(chuàng)建子子線程對(duì)象,所以current_thread()這個(gè)當(dāng)前線程就是子線程,子子線程沒(méi)有傳入daemon屬性值,所以創(chuàng)建時(shí)就把子線程的daemon屬性值作為該子子線程的daemon屬性值。
考慮這樣的場(chǎng)景,主線程只啟動(dòng)了子線程,子線程么有子子線程,子線程沒(méi)有設(shè)置daemon屬性時(shí),那就是誰(shuí)創(chuàng)建這個(gè)線程(當(dāng)然是主線程創(chuàng)建的),就把它的daemon屬性值作為這個(gè)線程的daemon值。主線程默認(rèn)的daemon值是False,所以這個(gè)子線程最終也會(huì)傳入的是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.")
運(yùn)行結(jié)果:
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。
.
總結(jié)
以上是生活随笔為你收集整理的[Python 多线程] 详解daemon属性值None,False,True的区别 (五)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: svn 红叉叉图标解决方法
- 下一篇: 使用PHP开发HR系统(5)