本文轉自https://www.freeaihub.com/article/threading-return-in-python.html,該頁可在線運行以下實例
Python的thread模塊,是不支持守護線程的。當主線程退出的時候,所有的子線程都將終止,不管他們是否仍在工作。本節我們開始介紹python的另外多線程模塊threading,該模塊支持守護線程,其工作方式:守護線程一般是一個等待客戶端請求的服務器。如果沒有客戶端請求,守護線程就是空閑的。如果把一個線程設置為守護線程,就表示這個線程是不重要的,進程退出時不需要等待這個線程執行完成。
如果主線程准備退出的時候,不需要等待某些子線程完成,就可以為這些子線程設置守護線程標記。該標記值為真的時候,標示線程是不重要的,或者說該線程只是用來等待客戶端請求而不做其他任何事情。使用下面的語句:thread.daemon=True 可以將一個線程設置為守護線程。同樣的也可以通過這個值來查看線程的守護狀態。一個新的子線程會繼承父線程的守護標記。整個python程序(也可以稱作主線程)將在所有的非守護線程退出之后才退出。
threading模塊除了Thread類之外,還包括許多好用的同步機制:
對象 | 描述 |
---|---|
Thread | 表示一個執行線程的對象 |
Lock | 鎖對象 |
RLock | 可重入鎖對象,使單一線程可以(再次)獲得已持有的鎖(遞歸鎖) |
Condition | 條件變量對象,使得一個線程等待另外一個線程滿足特定的條件,比如改變狀態或者某個數據值 |
Event | 條件變量的通用版本,任意數量的線程等待某個事件的發生,在該事件發生后所有的線程都將被激活 |
Semaphore | 為線程間的有限資源提供一個計數器,如果沒有可用資源時會被阻塞 |
BoundedSemaphore | 於Semaphore相似,不過它不允許超過初始值 |
Timer | 於Thread類似,不過它要在運行前等待一定時間 |
Barrier | 創建一個障礙,必須達到指定數量的線程后才可以繼續 |
其中,Thread類是threading模塊的主要執行對象。
下面是Thread類的屬性和方法列表:
屬性 | 描述 |
---|---|
Thread類屬性 | |
name | 線程名 |
ident | 線程的標識符 |
daemon | 布爾值,表示這個線程是否是守護線程 |
Thread類方法 | |
init(group=None,target=None,name=None,args=(),kwargs={},verbose=None,daemon=None) | 實例化一個線程對象,需要一個可調用的target對象,以及參數args或者kwargs。還可以傳遞name和group參數。daemon的值將會設定thread.daemon的屬性 |
start() | 開始執行該線程 |
run() | 定義線程的方法。(通常開發者應該在子類中重寫) |
join(timeout=None) | 直至啟動的線程終止之前一直掛起;除非給出了timeout(單位秒),否則一直被阻塞 |
getName() | 返回線程名(該方法已被棄用) |
setName() | 設定線程名(該方法已棄用) |
isAlive | 布爾值,表示這個線程是否還存活(駝峰式命名,python2.6版本開始已被取代) |
isDaemon() | 布爾值,表示是否是守護線程(已經棄用) |
setDaemon(布爾值) | 在線程start()之前調用,把線程的守護標識設定為指定的布爾值(已棄用) |
使用Thread類,可以有多種方法創建線程:
- 創建Thread類的實例,傳遞一個函數
- 創建Thread類的實例,傳遞一個可調用的類實例
- 派生Thread類的子類,並創建子類的實例
一般的,我們會采用第一種或者第三種方法。如果需要一個更加符合面向對象的接口時,傾向於選擇第三種方法,否則就用第一種方法吧。
第一種方法:創建Thread類,傳遞一個函數
下面的腳本中,我們先實例化Thread類,並傳遞一個函數(及其參數),當線程執行的時候,函數也會被執行:
import threading
from time import sleep,ctime
#不再把4秒和2秒硬性的編碼到不同的函數中,而是使用唯一的loop()函數,並把這些常量放進列表loops中
loops=[4,2]
def loop(nloop,nsec):
print('開始循環',nloop,'at:',ctime())
sleep(nsec)
print('循環',nloop,'結束於:',ctime())
def main():
print('程序開始於:',ctime())
threads=[]
nloops=range(len(loops))
for i in nloops:
t=threading.Thread(target=loop,args=(i,loops[i])) #循環 實例化2個Thread類,傳遞函數及其參數,並將線程對象放入一個列表中
threads.append(t)
for i in nloops:
threads[i].start() #循環 開始線程
for i in nloops:
threads[i].join() #循環 join()方法可以讓主線程等待所有的線程都執行完畢。
print('任務完成於:',ctime())
main()
和thread模塊相比,不同點在於:實現同樣的效果,thread模塊需要鎖對象,而threading模塊的Thread類不需要。實例化Thread(調用Thread())和調用thread.start_new_thread()的最大區別就是新線程不會立即執行!這是一個非常有用的同步功能,尤其在我們不希望線程立即開始執行的時候。
當所有的線程都分配完成之后,通過調用每個線程的start()方法再讓他們開始。相比於thread模塊的管理一組鎖(分配、獲取、釋放檢查鎖狀態)來說,threading模塊的Thread類只需要為每個線程調用join()方法即可。join(timeout=None)方法將等待線程結束,或者是達到指定的timeout時間時。這種鎖又稱為自旋鎖。
最重要的是:join()方法,其實你根本不需要調用它。一旦線程啟動,就會一直執行,直到給定的函數完成后退出。如果主線程還有其他事情要做(並不需要等待這些線程完成),可以不調用join()。join()只有在你需要等待線程完成時候才是有用的。
例如上面的腳本中,我們注釋掉join()代碼:
import threading
from time import sleep,ctime
#不再把4秒和2秒硬性的編碼到不同的函數中,而是使用唯一的loop()函數,並把這些常量放進列表loops中
loops=[4,2]
def loop(nloop,nsec):
print('開始循環',nloop,'at:',ctime())
sleep(nsec)
print('循環',nloop,'結束於:',ctime())
def main():
print('程序開始於:',ctime())
threads=[]
nloops=range(len(loops))
for i in nloops:
t=threading.Thread(target=loop,args=(i,loops[i])) #循環 實例化2個Thread類,傳遞函數及其參數,並將線程對象放入一個列表中
threads.append(t)
for i in nloops:
threads[i].start() #循環 開始線程
#for i in nloops:
#threads[i].join() #循環 join()方法可以讓主線程等待所有的線程都執行完畢。
print('任務完成於:',ctime())
main()
我們發現:主線程的任務比兩個循環線程要先執行(任務完成於……在 循環X結束……的前面)
第二種方法:創建Thread類的實例,傳遞一個可調用的類實例
創建線程時,於傳入函數類似的方法是傳入一個可調用的類的實例,用於線程執行——這種方法更加接近面向對象的多線程編程。比起一個函數或者從一個函數組中選擇而言,這種可調用的類包含一個執行環境,有更好的靈活性。
在上述的mtsleepC.py腳本中添加一個新類ThreadFunc,稍微改動一番,形成mtsleepD.py文件:
#!/usr/bin/env python
import threading
from time import sleep,ctime
loops=[4,2]
class ThreadFunc(object):
def __init__(self,func,args,name=''):
self.name=name
self.func = func
self.args=args
def __call__(self):
self.func(*self.args)
def loop(nloop,nsec):
print('開始循環',nloop,'在:',ctime())
sleep(nsec)
print('結束循環',nloop,'於:',ctime())
def main():
print('程序開始於:',ctime())
threads = []
nloops = range(len(loops))
for i in nloops:
t = threading.Thread(target=ThreadFunc(loop,(i,loops[i]),loop.__name__)) #傳遞一個可調用類的實例
threads.append(t)
for i in nloops:
threads[i].start() #開始所有的線程
for i in nloops:
threads[i].join() #等待所有的線程執行完畢
print('任務完成於:',ctime())
main()
上述腳本中,主要添加了ThreadFunc類,並在實例化Thread對象時,通過傳參的形式同時實例化了可調用類ThreadFunc。這里同時完成了兩個實例化。
我們研究一下創建ThreadFunc類的思想:我們希望這個類更加通用,而不是局限於loop()函數,為此,添加了一些新的東西,比如這個類保存了函數自身、函數的參數、以及函數名。構造函數__init__()用於設定上述值。當創建新線程的時候,Thread類的代碼將調用ThreadFunc對象,此時會調用__call__()這個特殊方法。
(老實說,這種方法顯得有些尷尬,並且稍微難以閱讀)
第三種方法:派生Thread的子類,並創建子類的實例
和方法二相比,方法三再創建線程時使用子類要相對更容易閱讀,下面是mtsleepE.py腳本:
#!/usr/bin/env pyhton
import threading
from time import sleep,ctime
loops=[4,2]
class MyThread(threading.Thread):
def __init__(self,func,args,name=''):
threading.Thread.__init__(self)
self.name = name
self.func = func
self.args = args
def run(self):
self.func(*self.args)
def loop(nloop,nsec):
print('開始循環',nloop,'在:',ctime())
sleep(nsec)
print('結束循環',nloop,'於:',ctime())
def main():
print('程序開始於:',ctime())
threads = []
nloops = range(len(loops))
for i in nloops:
t = MyThread(loop,(i,loops[i]),loop.__name__)
threads.append(t)
for i in nloops:
threads[i].start()
for i in nloops:
threads[i].join()
print('所有的任務完成於:',ctime())
main()
比較方法二和方法三,重要的變化在於:MyThread子類的構造函數必須先調用其父類的構造函數;重寫run()方法,代替方法二中的__call__()方法。
優化第三種方法:
對MyThread類進行修改,增加一些調試信息的輸出,另外,除了簡單的調用函數外,還可以將結果保存在實例的屬性self.res中,同時增加新的方法getResult()來獲取這個值:
以下代碼比較了遞歸求斐波那契、階乘和累計函數的執行,分別按照單線程和多線程的方式執行同樣的任務:
#!/usr/bin/env python
import threading
from time import ctime,sleep
#對MyThread進行封裝
class MyThread(threading.Thread):
def __init__(self,func,args,name=''):
threading.Thread.__init__(self)
self.func = func
self.name = name
self.args = args
def run(self):
print('開始執行',self.name,' 在:',ctime())
self.res = self.func(*self.args)
print(self.name,'結束於:',ctime())
def getResult(self):
return self.res
#斐波那契
def fib(x):
sleep(0.005)
if x < 2:
return 1
return fib(x-1)+fib(x-2)
#階乘
def fac(x):
sleep(0.1)
if x < 2:
return 1
return x*fac(x-1)
#累加
def sum(x):
sleep(0.1)
if x < 2 :
return 1
return x + sum(x-1)
funcs=[fib,fac,sum]
n = 12
def main():
nfuncs = range(len(funcs))
#單線程
print('單線程模式')
for i in nfuncs:
print('開始',funcs[i].__name__,' 在:',ctime())
print(funcs[i](n))
print(funcs[i].__name__,'結束於:',ctime())
#多線程
print('多線程模式')
threads = []
for i in nfuncs :
t = MyThread(funcs[i],(n,),funcs[i].__name__)
threads.append(t)
for i in nfuncs:
threads[i].start()
for i in nfuncs:
threads[i].join()
print(threads[i].getResult())
print('所有的任務結束')
main()
總結
程序中,為了看到多線程如何改善性能的,我們加入了sleep函數用於減慢執行速度。
看到單線程模式中,只是簡單的一次調用每個函數,並在函數結束執行的時候立即顯示相關的結果;而使用多線程的時候,並不會立刻顯示結果,因為我們希望MyThread類越通用越好(有輸出和無輸出都能執行),我們一直等到所有線程都join之后,再調用getResult()方法顯示每個函數的返回值。