「Python-StandardLib」第十六章:并发执行( Cocurrent Executing,线程、多线程队列、子进程)
參考鏈接:
python多線程
python線程——基于線程的并行
16.1 線程(threading)
ps: python ver. is 2.7.18
線程是一項將非連續依賴任務進行分解的技術。線程能提高應用的響應能力,它接收用戶的輸入而其他任務放在后臺運行。一個相關的用例是:進行I/O交互的同時也在進行計算(cpu執行命令,被稱作計算)工作。
16.1.1 線程對象:threading.Thread
在python2.7版本中,線程是通過類創建的,一個類實例對應一個獨立的線程。且threading模塊是基于thread模塊的高水平實現。
該類對象代表運行在一個獨立的線程中的活動。可以通過兩種方式來指定活動:傳遞一個可調用對象到constructor方法
,或者在一個子類中重寫run方法(除了constructor和run可以被覆蓋,其他子類中的任何方法都不允許被覆蓋;換句話講,只能覆蓋類中的__init__()和run()方法)。
一旦線程被創建,線程活動必須通過調用線程的start()方法來啟動,start()方法在單獨的控制線程中調用run()方法。
一旦線程活動開始,那么該線程的狀態將被視作’alive’,當run()方法終止時(包括正常終止/引發未處理異常)該’alive’狀態停止。可以通過調用is_alive()方法判斷線程活動是否處理alive狀態。
其他線程可以調用線程的join()方法,它會阻塞當前調用的線程直到調用join()方法的線程終止。
每個線程都有一個名字,名字可以被傳遞到constructor,通過name進行讀取或修改。
線程可以被標記為"daemon"線程,其意義是當只剩daemon線程時,整個python程序才會退出。它的初始值由創建線程時繼承。標志可以通過daemon屬性設置。
Note : Daemon threads are abruptly stopped at shutdown. Their resources (such as open files, database transactions, etc.) may not be released properly. If you want your threads to stop gracefully, make them non-daemonic and use a suitable signalling mechanism such as an Event.
除此之外,還有一個“主線程”對象,它對應python程序中初始的控制線程,但并不是daemon線程。
還存在一種“虛擬線程對象”被創建的可能。這些線程對象與“外來線程”對應,屬于在線程模塊外啟動的控制線程,例如從C代碼中產生的線程。虛擬線程對象的功能有限,通常被看做alive狀態和daemon線程,且不能被join()。這些線程永遠不會被刪除,因為外來線程已經超出了python程序的管理范圍。
類名稱
class threading.Thread(group=None, target=None, name=None, args=(), kwargs={})
該constructor通常應該通過關鍵詞參數調用
類關鍵詞
- group:應該為None,為未來擴展作保留
- target:是一個可調用的對象,可以通過run()函數調用,默認為None,意思沒有被調用的東西。
- name:線程名,默認使用“Thread-N”這種獨特的名字進行構建,其中N是一個小的十進制數
- args:一個目標調用的元祖,默認為()
- kwargs:目標調用關鍵詞參數的一個字典,默認為{}
如果子類要覆蓋constructor,必須要確保在做任何其他工作前基礎constructor(Thread.init())被調用。
類方法
- start():啟動一個線程活動,每個線程最多必須調用一次,它為對象的run()方法在分離的控制線程中被調用做準備
- run():代表線程活動的方法,可以在子類中重寫該方法。標準的run()方法調用被傳遞到constructor、作為target參數的可調用對象,如果有的話,順序參數和關鍵字參數分別取自args和kwargs
- join([timeout]):當代直到線程終止。它將阻塞調用線程直到帶join的線程結束(包括正常終止/未處理異常/到時事件出現)。當timeout參數并沒有給定或者是None時,該操作會阻塞直到線程終止。一個線程可以被join()ed多次。如果加入一個當前的線程會導致死鎖,那么join()方法會報RuntimeError;此外,在線程還未啟動時就調用join方法也會出現同樣的報錯。
- name:一串用于確認的字符串,沒有語義信息,在多線程中可能會被基于同樣的name,其出事name由constructor設定。
- ident:表示該線程的線程標志器,如果線程沒有啟動則為None。是一個非零整數,當一個線程存在并且另一個線程被創建時,線程標志器會被回收。標志器即使線程退出后也仍然可以使用。
- is_alive()/isAlive():返回線程是否是alive狀態
- daemon:一個布爾值,表明該線程是否是daemon線程(True是False不是),該屬性必須在start()方法前設置,否則會出現RuntimeError,它的初始值繼承于線程創建;主線程并不是daemon線程,因此所有在主線程中被創建的線程的daemon屬性都會被設置為False
- isDaemon()/setDaemon():詳見daemon模組
例子
一個threading.Tread代碼例子
import threading, zipfileclass AsyncZip(threading.Thread):def __init__(self, infile, outfile):threading.Thread.__init__(self)self.infile = infileself.outfile = outfiledef run(self):f = zipfile.ZipFile(self.outfile, 'w', zipfile.ZIP_DEFLATED)f.write(self.infile)f.close()print('Finished background zip of:', self.infile)background = AsyncZip('mydata.txt', 'myarchive.zip') background.start() print('The main program continues to run in foreground.')background.join() # Wait for the background task to finish print('Main program waited until background was done.')16.2 多線程(multiprocessing):基于進程的線程交互
multiprocessing是一個支持類似threading模塊包,它支持使用API來產生進程。multiprocessing提供本地和遠程并發,通過使用子進程而不是線程的方法高效地繞過了全局解釋器鎖。因此,multiprocessing模塊允許程序員全面利用機器的多核高效開展工作。multiprocessing支持在Unix和Windows上運行。
multiprocessing模塊還引進了threading模塊中沒有的API。下面有一個簡單的例子,Pool對象,它提供了一個接收多輸入值函數并列執行的便捷方法,它將輸入數據分發到各個進程中(數據并行)。下面的例子展示了上面提到的內容:
from multiprocessing import Pooldef f(x):return x*xif __name__ == '__main__':p = Pool(5)print(p.map(f, [1, 2, 3]))將會得到如下輸出:
[1, 4, 9]16.2.1 多線程對象:multiprocessing.Process
在multiprocessing中,進程通過調用一個Process對象并調用start()方法產生。Process遵循threading.Thread的API。一個multiprocessing程序的簡單樣例如下:
from multiprocessing import Process # import Process模塊def f(name): # 功能函數定義print 'hello', nameif __name__ == '__main__':p = Process(target=f, args=('bob',)) # 創建Process對象(其中target是f函數,f函數接收的參數是'bob')并執行初始化p.start() # 啟動進程p.join() # 加入一個子進程為了展示上面包括的各個進程的IDs,下面是一個擴展樣例:
from multiprocessing import Process import osdef info(title):print titleprint 'module name:', __name__if hasattr(os, 'getppid'): # only available on Unixprint 'parent process:', os.getppid()print 'process id:', os.getpid()def f(name):info('function f')print 'hello', nameif __name__ == '__main__':info('main line')p = Process(target=f, args=('bob',))p.start()p.join()類名稱
class multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={})
Process對象代表了運行在分離進程中的活動,Process類擁有threading.Thread類相同的所有方法。constructor仍然需要帶關鍵字調用。
類關鍵詞
- group:總默認為None,它存在的意義只是為了兼容threading.Thread
- target:是可調用對象,被run()方法調用,默認為None
- name:進程名,形如‘Process-N1:N2:…:Nk’
- args:target調用中使用的參數
- kwargs:target調用中關鍵字參數的一個字典
類方法
- start():啟動一個進程活動,每個進程對象最多必須調用一次,它為run()方法在分離的進程中被調用做準備
- run():代表進程活動,可以在子類中重寫該方法。標準的run()方法調用被傳遞到constructor、作為target參數的可調用對象(例子中的函數f),如果有的話,順序參數和關鍵字參數分別取自args和kwargs
- join([timeout]):直到線程終止。它將阻塞調用線程直到帶join的線程結束(包括正常終止/未處理異常/到時事件出現)。當timeout參數并沒有給定或者是None時,該操作會阻塞直到線程終止。一個線程可以被join()ed多次。如果加入一個當前的線程會導致死鎖,那么join()方法會報RuntimeError;此外,在線程還未啟動時就調用join方法也會出現同樣的報錯。
- name:一串用于確認的字符串,沒有語義信息,在多線程中可能會被基于同樣的name,其出事name由constructor設定。
- is_alive()/isAlive():返回線程是否是alive狀態
- daemon:一個布爾值,表明該線程是否是daemon線程(True是False不是),該屬性必須在start()方法前設置,否則會出現RuntimeError,它的初始值繼承于線程創建;主線程并不是daemon線程,因此所有在主線程中被創建的線程的daemon屬性都會被設置為False
下面是Process中新加入、threading.Thread中沒有的屬性: - pid:返回進程ID,進程產生前該值為None
- exitcode:子退出代碼,如果進程沒有終止,該值為None。負值-N表示子進程N終止
- authkey:進程的認證密鑰,當multiprocessing被初始化后,主進程將被賦予一個隨機的字符串。當Process對象被創建時,它將從它的父進程中繼承認證密鑰,但可以通過設置authkey屬性來改變。
- terminate():終止進程。在Unix系統中通過使用SIGTERM信號來終止。不會執行退出處理程序和 finally 子句等,進程的后代進程也不會被終止——只會成為孤立的進程。
start(), join(), is_alive(), terminate(), exitcode方法應該只在進程創建后調用
例子
下面是一個調用Process,
>>> import multiprocessing, time, signal >>> p = multiprocessing.Process(target=time.sleep, args=(1000,)) >>> print p, p.is_alive() <Process(Process-1, initial)> False >>> p.start() >>> print p, p.is_alive() <Process(Process-1, started)> True >>> p.terminate() >>> time.sleep(0.1) >>> print p, p.is_alive() <Process(Process-1, stopped[SIGTERM])> False >>> p.exitcode == -signal.SIGTERM True上面,很完美的一個例子,懂的都懂
16.3 不可重入的線程鎖(Lock)
原語鎖是一種原始的同步方法,當它上鎖后并不會某一線程專有,其他線程也可以進行解鎖。
原語鎖只有兩種狀態:locked(鎖)和unlocked(非鎖)。創建原語鎖時是處于unlocked狀態。
原語鎖類有兩個方法:acquire()和realease()。當調用acquire時,該方法對整個進程(?應該是進程)進行上鎖,直到release方法被其他線程調用(疑問:調用鎖的線程不可以調用release嗎?可以調用)
16.3.1 線程原語鎖對象:threading.Lock
類名稱
class threading.Lock
沒有初始化參數
類關鍵字
None
類方法
- acqure():上鎖,阻塞后返回True。該方法有一個blocking參數,默認為True,默認阻塞進程,也可以設置為不阻塞,具體見官網方法說明
- release():釋放鎖,任何進程都可以調用該方法(包括上鎖進程本身),無返回值
- locked():返回鎖是否存在,如果鎖存在則返回True
例子
None
16.4 可重入的線程鎖(RLock)
總結
以上是生活随笔為你收集整理的「Python-StandardLib」第十六章:并发执行( Cocurrent Executing,线程、多线程队列、子进程)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么设置刷新率 刷新率设置方法详解
- 下一篇: 如何在家里制作健康的烤蔬菜串?