單線程執行 python的內置模塊提供了兩個內置模塊:thread和threading,thread是源生模塊,threading是擴展模塊,在thread的基礎上進行了封裝及改進。所以只需要使用threading這個模塊就能完成併發的測試 實例 創建並啟動一個單線程 執行結果 其實單線程的執行結果 ...
單線程執行
python的內置模塊提供了兩個內置模塊:thread和threading,thread是源生模塊,threading是擴展模塊,在thread的基礎上進行了封裝及改進。所以只需要使用threading這個模塊就能完成併發的測試
實例
創建並啟動一個單線程
import threading def myTestFunc(): print("我是一個函數") t = threading.Thread(target=myTestFunc) # 創建一個線程 t.start() # 啟動線程
執行結果
C:\Python36\python.exe D:/MyThreading/myThread.py
我是一個線程函數
Process finished with exit code 0
其實單線程的執行結果和單獨執行某一個或者某一組函數結果是一樣的,區別隻在於用線程的方式執行函數,而線程是可以同時執行多個的,函數是不可以同時執行的。
多線程執行
上面介紹了單線程如何使用,多線程只需要通過迴圈創建多個線程,並迴圈啟動線程執行就可以了
實例
import threading from datetime import datetime def thread_func(): # 線程函數 print('我是一個線程函數', datetime.now()) def many_thread(): threads = [] for _ in range(10): # 迴圈創建10個線程 t = threading.Thread(target=thread_func) threads.append(t) for t in threads: # 迴圈啟動10個線程 t.start() if __name__ == '__main__': many_thread()
執行結果
C:\Python36\python.exe D:/MyThreading/manythread.py 我是一個線程函數 2019-06-23 16:54:58.205146 我是一個線程函數 2019-06-23 16:54:58.205146 我是一個線程函數 2019-06-23 16:54:58.206159 我是一個線程函數 2019-06-23 16:54:58.206159 我是一個線程函數 2019-06-23 16:54:58.206159 我是一個線程函數 2019-06-23 16:54:58.207139 我是一個線程函數 2019-06-23 16:54:58.207139 我是一個線程函數 2019-06-23 16:54:58.207139 我是一個線程函數 2019-06-23 16:54:58.208150 我是一個線程函數 2019-06-23 16:54:58.208150 Process finished with exit code 0
通過迴圈創建10個線程,並且執行了10次線程函數,但需要註意的是python的併發並非絕對意義上的同時處理,因為啟動線程是通過迴圈啟動的,還是有先後順序的,通過執行結果的時間可以看出還是有細微的差異,但可以忽略不記。當然如果線程過多就會擴大這種差異。我們啟動500個線程看下程式執行時間
實例
import threading from datetime import datetime def thread_func(): # 線程函數 print('我是一個線程函數', datetime.now()) def many_thread(): threads = [] for _ in range(500): # 迴圈創建500個線程 t = threading.Thread(target=thread_func) threads.append(t) for t in threads: # 迴圈啟動500個線程 t.start() if __name__ == '__main__': start = datetime.today().now() many_thread() duration = datetime.today().now() - start print(duration)
執行結果
0:00:00.111657
Process finished with exit code 0
500個線程共執行了大約0.11秒
那麼針對這種問題我們該如何優化呢?我們可以創建25個線程,每個線程執行20次線程函數,這樣在啟動下一個線程的時候,上一個線程已經在迴圈執行了,這樣就大大減少了併發的時間差異
優化
import threading from datetime import datetime
def thread_func(): # 線程函數 print('我是一個線程函數', datetime.now()) def execute_func(): for _ in range(20): thread_func() def many_thread(): start = datetime.now() threads = [] for _ in range(25): # 迴圈創建500個線程 t = threading.Thread(target=execute_func) threads.append(t) for t in threads: # 迴圈啟動500個線程 t.start() duration = datetime.now() - start print(duration) if __name__ == '__main__': many_thread()
輸出結果(僅看程式執行間隔)
0:00:00.014959
Process finished with exit code 0
後面的優化執行500次併發一共花了0.014秒。比未優化前的500個併發快了幾倍,如果線程函數的執行時間比較長的話,那麼這個差異會更加顯著,所以大量的併發測試建議使用後者,後者比較接近同時“併發”
守護線程
多線程還有一個重要概念就是守護線程。那麼在這之前我們需要知道主線程和子線程的區別,之前創建的線程其實都是main()線程的子線程,即先啟動主線程main(),然後執行線程函數子線程。
那麼什麼是守護線程?即當主線程執行完畢之後,所有的子線程也被關閉(無論子線程是否執行完成)。預設不設置的情況下是沒有守護線程的,主線程執行完畢後,會等待子線程全部執行完畢,才會關閉結束程式。
但是這樣會有一個弊端,當子線程死迴圈了或者一直處於等待之中,則程式將不會被關閉,被被無限掛起,我們把上述的線程函數改成迴圈10次, 並睡眠2秒,這樣效果會更明顯
import threading from datetime import datetime import time
def thread_func(): # 線程函數
time.sleep(2) i = 0 while(i < 11): print(datetime.now()) i += 1 def many_thread(): threads = [] for _ in range(10): # 迴圈創建500個線程 t = threading.Thread(target=thread_func) threads.append(t) for t in threads: # 迴圈啟動500個線程 t.start() if __name__ == '__main__': many_thread() print("thread end")
執行結果
C:\Python36\python.exe D:/MyThreading/manythread.py
thread end
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.468612
2019-06-23 19:08:00.469559
2019-06-23 19:08:00.469559
2019-06-23 19:08:00.469559
2019-06-23 19:08:00.469559
2019-06-23 19:08:00.469559
2019-06-23 19:08:00.469559
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.470556
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.471554
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.472557
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.473548
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.474545
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.475552
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.476548
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
2019-06-23 19:08:00.477546
Process finished with exit code 0
根據上述結果可以看到主線程列印了“thread end”之後(主線程結束),子線程還在繼續執行,並未隨著主線程的結束而結束
下麵我們通過 setDaemon方法給子線程添加守護線程,我們把迴圈改為死迴圈,再來看看輸出結果(註意守護線程要加在start之前)
import threading from datetime import datetime def thread_func(): # 線程函數 i = 0 while(1): print(datetime.now()) i += 1 def many_thread(): threads = [] for _ in range(10): # 迴圈創建500個線程 t = threading.Thread(target=thread_func) threads.append(t) t.setDaemon(True) # 給每個子線程添加守護線程 for t in threads: # 迴圈啟動500個線程 t.start() if __name__ == '__main__': many_thread() print("thread end")
輸出結果
2019-06-23 19:12:35.564539
2019-06-23 19:12:35.564539
2019-06-23 19:12:35.564539
2019-06-23 19:12:35.564539
2019-06-23 19:12:35.564539
2019-06-23 19:12:35.564539
2019-06-23 19:12:35.565529
2019-06-23 19:12:35.565529
2019-06-23 19:12:35.565529
thread end
Process finished with exit code 0
通過結果我們可以發現,主線程關閉之後子線程也會隨著關閉,並沒有無限的迴圈下去,這就像程式執行到一半強制關閉執行一樣,看似暴力卻很有用,如果子線程發送一個請求未收到請求結果,那不可能永遠等下去,這時候就需要強制關閉。所以守護線程解決了主線程和子線程關閉的問題。
阻塞線程
上面說了守護線程的作用,那麼有沒有別的方法來解決上述問題呢? 其實是有的,那就是阻塞線程,這種方式更加合理,使用join()方法阻塞線程,讓主線程等待子線程執行完成之後再往下執行,再關閉所有子線程,而不是只要主線程結束,不管子線程是否執行完成都終止子線程執行。下麵我們給子線程添加上join()(主要join要加到start之後)
import threading from datetime import datetime import time def thread_func(): # 線程函數 time.sleep(1) i = 0 while(i < 11): print(datetime.now()) i += 1 def many_thread(): threads = [] for _ in range(10): # 迴圈創建500個線程 t = threading.Thread(target=thread_func) threads.append(t) t.setDaemon(True) # 給每個子線程添加守護線程 for t in threads: # 迴圈啟動500個線程 t.start() for t in threads: t.join() # 阻塞線程 if __name__ == '__main__': many_thread() print("thread end")
執行結果
程式會一直執行,但是不會列印“thread end”語句,因為子線程並未結束,那麼主線程就會一直等待。
疑問:有人會覺得這和什麼都不設置是一樣的,其實會有一點區別的,從守護線程和線程阻塞的定義就可以看出來,如果什麼都沒設置,那麼主線程會先執行完畢列印後面的“thread end”,而等待子線程執行完畢。兩個都設置了,那麼主線程會等待子線程執行結束再繼續執行。
而對於死迴圈或者一直等待的情況,我們可以給join設置超時等待,我們設置join的參數為2,那麼子線程會告訴主線程讓其等待2秒,如果2秒內子線程執行結束主線程就繼續往下執行,如果2秒內子線程未結束,主線程也會繼續往下執行,執行完成後關閉子線程
import threading from datetime import datetime import time def thread_func(): # 線程函數 time.sleep(1) i = 0 while(1): print(datetime.now()) i += 1 def many_thread(): threads = [] for _ in range(10): # 迴圈創建500個線程 t = threading.Thread(target=thread_func) threads.append(t) t.setDaemon(True) # 給每個子線程添加守護線程 for t in threads: # 迴圈啟動500個線程 t.start() for t in threads: t.join(2) # 設置子線程超時2秒 if __name__ == '__main__': many_thread() print("thread end")
輸出結果
你運行程式後會發現,運行了大概2秒的時候,程式會數據“thread end” 然後結束程式執行, 這就是阻塞線程的意義,控制子線程和主線程的執行順序
總結
最好呢,再次說一下守護線程和阻塞線程的定義
守護線程:子線程會隨著主線程的結束而結束,無論子線程是否執行完畢
阻塞線程:主線程會等待子線程的執行結束,才繼續執行