Python多線程
Python 提供了多個模塊來支持多線程編程,包括 thread、threading 和 Queue 模塊等。程序是可以使用 thread 和 threading 模塊來創建與管理線程。
thread 模塊提供了基本的線程和鎖定支持;而 threading 模塊提供了更高級別、功能更全面的線程管理。
使用 Queue 模塊,用戶可以創建一個隊列數據結構,用於在多線程之間進行共享。
在python3.0中,已經將thread改名為_thread
Python 代碼的執行是由 Python 虛擬機(又名解釋器主循環)進行控制的。
對 Python 虛擬機的訪問是由全局解釋器鎖(GIL)控制的。這個鎖就是用來保證同時只能有一個線程運行的。在多線程環境中,Python 虛擬機將按照下面所述的方式執行。
1.設置 GIL。
2.切換進一個線程去運行。
3.執行下面操作之一。
a.指定數量的字節碼指令。
b.線程主動讓出控制權(可以調用 time.sleep(0)來完成)。
4.把線程設置回睡眠狀態(切換出線程)。
5.解鎖 GIL。
6.重復上述步驟。
當一個線程完成函數的執行時,它就會退出。另外,還可以通過調用諸如 thread.exit()之類的退出函數,或者 sys.exit()之類的退出 Python 進程的標准方法,亦或者拋出 SystemExit異常,來使線程退出。不過,你不能直接“終止”一個線程。
python多線程支持的平台:
絕大多數類 UNIX 平台(如 Linux、Solaris、Mac OS X、*BSD 等),以及Windows 平台。
Python 使用兼容 POSIX 的線程,也就是pthread,點擊跳轉百度百科
不使用線程的情況
我們將使用 time.sleep()函數來演示線程是如何工作的
創建兩個時間循環:一個睡眠 4 秒(loop0());另一個睡眠 2 秒(loop1()) (這里使用“loop0”和“loop1”作為函數名,暗示我們最終會有一個循環序列)。
import time def loop0(): print("start loop 0 at: %s" % time.ctime()) time.sleep(4) print("loop 0 done at: %s" % time.ctime()) def loop1(): print("start loop 1 at: %s" % time.ctime()) time.sleep(2) print("loop 1 done at: %s" % time.ctime()) def main(): print("starting at: %s" % time.ctime()) loop0() loop1() print("all DONE at: %s" % time.ctime()) if __name__ == '__main__': main()

starting at: Tue Jan 23 16:03:21 2018 start loop 0 at: Tue Jan 23 16:03:21 2018 loop 0 done at: Tue Jan 23 16:03:25 2018 start loop 1 at: Tue Jan 23 16:03:25 2018 loop 1 done at: Tue Jan 23 16:03:27 2018 all DONE at: Tue Jan 23 16:03:27 2018 從輸出中我們可以看出,輸出整整花了我們7秒鍾的時間
_thread模塊-一個不建議使用的模塊
_thread模塊和鎖對象
thread 模塊的核心函數是 start_new_thread()。它的參數包括函數(對象)、函數的參數以及可選的關鍵字參數。
start_new_thread()必須包含開始的兩個參數,於是即使要執行的函數不需要參數,也需要傳遞一個空元組。
使用_thread模塊進行編程:
我們只需要將上面的代碼進行稍微改動一下即可
import time import _thread def loop0(): print("start loop 0 at: %s" % time.ctime()) time.sleep(4) print("loop 0 done at: %s" % time.ctime()) def loop1(): print("start loop 1 at: %s" % time.ctime()) time.sleep(2) print("loop 1 done at: %s" % time.ctime()) def main(): print("starting at: %s" % time.ctime()) _thread.start_new_thread(loop0, ()) _thread.start_new_thread(loop1, ()) time.sleep(5) #暫停5秒,原因是因為我們的loop0函數暫停了4秒,如果我們小於4秒會出現無法輸出loop 0 done... print("all DONE at: %s" % time.ctime()) if __name__ == '__main__': main()

starting at: Tue Jan 23 16:14:19 2018 start loop 0 at: Tue Jan 23 16:14:19 2018 start loop 1 at: Tue Jan 23 16:14:19 2018 loop 1 done at: Tue Jan 23 16:14:21 2018 loop 0 done at: Tue Jan 23 16:14:23 2018 all DONE at: Tue Jan 23 16:14:24 2018 從輸出結果中我們可以看到loop0和loop1同時啟動了,而不會先執行完loop0在執行loop1
守護線程
避免使用 thread 模塊的另一個原因是該模塊不支持守護線程這個概念。當主線程退出時,所有子線程都將終止,不管它們是否仍在工作。如果你不希望發生這種行為,就要引入守護線程的概念了。threading 模塊支持守護線程,其工作方式是:守護線程一般是一個等待客戶端請求服務的服務器。如果沒有客戶端請求,守護線程就是空閑的。如果把一個線程設置為守護線程,就表示這個線程是不重要的,進程退出時不需要等待這個線程執行完成。如同在第 2 章中看到的那樣,服務器線程遠行在一個無限循環里,並且在正常情況下不會退出。如果主線程准備退出時,不需要等待某些子線程完成,就可以為這些子線程設置守護線程標記。該標記值為真時,表示該線程是不重要的,或者說該線程只是用來等待客戶端請求而不做任何其他事情。要將一個線程設置為守護線程,需要在啟動線程之前執行如下賦值語句:thread.daemon = True(調用 thread.setDaemon(True)的舊方法已經棄用了)。同樣,要檢查線程的守護狀態,也只需要檢查這個值即可(對比過去調用 thread.isDaemon()的方法)。一個新的子線程會繼承父線程的守護標記。整個 Python 程序(可以解讀為:主線程)將在所有非守護線程退出之后才退出,換句話說,就是沒有剩下存活的非守護線程時。
threading模塊
threading模塊的對象
對象 |
描述 |
Thread |
表示一個執行線程的對象 |
Lock |
鎖原語對象(和 thread 模塊中的鎖一樣) |
RLock |
可重入鎖對象,使單一線程可以(再次)獲得已持有的鎖(遞歸鎖) |
Condition |
條件變量對象,使得一個線程等待另一個線程滿足特定的“條件”,比如改變狀態或某個數據值 |
Event |
條件變量的通用版本,任意數量的線程等待某個事件的發生,在該事件發生后所有線程將被激活 |
Semaphore |
為線程間共享的有限資源提供了一個“計數器”,如果沒有可用資源時會被阻塞 |
BoundedSemaphore |
與 Semaphore 相似,不過它不允許超過初始值 |
Timer |
與 Thread 相似,不過它要在運行前等待一段時間 |
Barrier |
創建一個“障礙”,必須達到指定數量的線程后才可以繼續 |
threading 模塊的 Thread 類是主要的執行對象。它有 thread 模塊中沒有的很多函數。
Thread 對象的屬性和方法
使用 Thread 類,可以有很多方法來創建線程。
創建Thread的實例,傳給它一個函數
代碼
import threading import time loops = [4, 2] def loop(nloop, nsec): print("start loop %s at: %s" % (nloop, time.ctime())) time.sleep(nsec) print("loop %s done at: %s" % (nloop, time.ctime())) def main(): print("starting at: %s" % time.ctime()) threads = [] nloops = range(len(loops)) for i in range(0, 2): t = threading.Thread(target=loop, args=(i, loops[i])) # print(type(i), i, type(loops[i]), loops[i]) # 這個位置可能容易暈,第一次傳入args,i會=0,loops[i]會等於4,第二次循環,i=1,loops[i]=2 # (i=0,loops[i]=4,為什么是0和4,因為i本來就=0,loops[i]里面的i=0,loops列表的第一零個本來就等於4) # 會傳入到loop函數中 threads.append(t) # 將每次循環的對象加入到列表 for i in nloops: threads[i].start() # 開啟線程 for i in nloops: # 等待 threads[i].join() # 線程完成 print("all Done at: %s" % time.ctime()) if __name__ == '__main__': main()

starting at: Wed Jan 24 20:54:03 2018 start loop 0 at: Wed Jan 24 20:54:03 2018 start loop 1 at: Wed Jan 24 20:54:03 2018 loop 1 done at: Wed Jan 24 20:54:05 2018 loop 0 done at: Wed Jan 24 20:54:07 2018 all Done at: Wed Jan 24 20:54:07 2018