Python 多執行緒程序撰寫問題 和 注意事項
1. 前言
其實這一節應該排在 Concurrency Concepts in Python 這節的後面,這樣比較能看懂 Fred 老師所想要闡述及表達的重點。
因此在進入這個主題之前,建議先看完 Concurrency Concepts in Python 這節的內容。而這邊我也稍微整理一下 Concurrency Concepts in Python 這節的內容,方便大家理解這一節的內容。
此外因為這一節的範例雖精彩但範例頗多導致內容頗長,因此我並不打算在這裡重複所有的範例,就當作大家都已看過 Python Threading - Issues and Caveats 這節視頻教學及所有範例,轉而希望能多些篇幅來討論這當中關於 Python Threading 使用的操作重點。至於詳細的範例分析可參考我轉譯老師本節的範例及內容中英對照的 Jupyter Notebook 筆記。
2. Fred 老師關於 Python 併發程序的概念的重點摘要
併發是一種程序或演算法的結構化方式,表示程式碼可以以無序或者部分有序的方式執行,而不影響最終結果。 → 併發程式碼段不一定按特定順序執行,但最終結果保持不變。
CPython 基於 GIL (Global Interpreter Lock) 限制了 Python 的併發能力,只允許交叉運行多個執行緒,換言之,即使建立多個執行緒也只允許一個執行緒運行,這對多CPU沒有幫助,但對於I/O密集型的任務則有幫助。
每個處理程序(Task/Process)使用一個或多個執行緒執行,至少有一個主執行緒(Main Thread),而併發程式碼可以使用多執行緒執行。
不同執行緒共享處理程序資源(全域資料,打開的檔案等),也可以有個別執行緒的"私有"資源(局部變數),而作業系統有排程器,決定何時運行執行緒,而排程器決定何時切換,開發人員無從預測也無法控制。
關於 Python Concurrent Concepts 的更詳細內容說明請參考我前一篇關於 Python 中的併發程序概念的重點摘要 筆記。
3. Python 多執行緒的程式起手及執行架構
- 由於大家可能之前並沒接觸過 Python 多執行緒的程式,因此這邊我先簡單介紹一下 Python 多執行緒的程式起手及執行架構。
- Python 多執行緒的程式起手及執行架構如下:
import threading
# 宣告一個常數來定義要啟動的執行緒的數量
THREAD_NUM = 10
def do_function_inside_thread():
'''宣告一個要在 thread 中執行的函式'''
PASS
# 宣告一個 list 來存放所有的 thread
threads = []
# 創建 threads
for i in range(THREAD_NUM):
thread = threading.Thread(target=do_function_inside_thread)
threads.append(thread)
# 啟動 threads
for thread in threads:
thread.start()
# 等待所有的 threads 完成
for thread in threads:
thread.join()
[Note]:
如有需要 thread lock 的時候,可以先如下宣告:
lock = threading.Lock()
def do_function_inside_thread1():
''' function code outside lock '''
with lock:
''' code inside lock '''
''' function code outside lock '''
def do_function_inside_thread2():
''' function code outside lock '''
lock.acquire()
''' code inside lock '''
lock.release()
''' function code outside lock '''
# 其他和一般 multi-threading code 做法相同
# 不同目的的鎖定,可以宣告更多的lock來包住需要 lock 的程式碼
4. Python 多執行緒程序撰寫問題和注意事項
-
由上述 Python 併發程序的概念,我們知道幾個問題:
-
由於 GIL 的存在,Python 的執行緒無法利用多核心的優勢,因此 Python 的執行緒適合用於 I/O密集型的任務,而不適合用於 CPU密集型的任務。不過這裡部影片的重點不探討這個部分。
-
事實上但凡有順序性的任務,都不適合使用多執行緒,因為多執行緒的執行,是搶佔方式,並不保證執行的順序,而執行緒的切換也會導致效能下降。
-
Python 多執行緒,適合用於 I/O密集型的任務,例如網路請求,檔案讀寫等等,因為這些任務不強調順序性,且任務的執行時間,大部分都是在等待 I/O 的回應,而不是在執行計算。
-
-
由於執行緒共享處理程序資源,因此需要注意資源的同步問題,避免資源的競爭問題。
- 由於執行緒是透過搶佔式的方式來執行,因此當多個執行緒同時存取同一個資源時,可能會導致資源的競爭問題,進而導致資源的同步問題,因此需要注意資源的同步問題,避免資源的競爭問題。
-
由於執行緒共享處理程序資源,因此需要注意資源的執行緒安全問題,避免資源的競爭問題。
-
如果程式碼中採用的函式或方法不是執行緒安全的,可能會出現因搶佔及執行順序所帶來的程序執行問題。
-
因此有非執行緒安全的函式或方法,需要透過加鎖的方式來確保執行緒安全。
-
Python 也提供了一些執行緒安全的函式或方法,例如 queue.Queue 類別,可以用來解決執行緒安全的問題。
-
-
由於執行緒共享處理程序資源,因此需要注意資源的死鎖問題,避免資源的競爭問題。
- 當共享資源的執行緒互相等待對方釋放資源時,可能會導致資源的死鎖問題,因此需要注意資源的死鎖問題,避免資源的競爭問題。
-
由於執行緒共享處理程序資源,因此需要注意資源的競爭問題,避免資源的競爭問題。
- 當資源發生競爭問題時,可能會導致資源的同步問題,執行緒安全問題,死鎖問題,因此需要注意資源的競爭問題,避免資源的競爭問題。
-
-
上述列出的這些問題,也就是Fred 老師這節視頻針對多執行緒程序撰寫的探討主軸:
-
有無必要使用多執行緒?
-
多執行緒一定就比單執行緒好嗎?
-
多執行緒程序撰寫的陷阱為何?
Fred 老師依舊利用了他最擅長的循序完善的程式碼演化來幫助我們了解多執行緒程序當中可能會引發的種種問題。
甚至也透過效率的分析,讓我們知道:
並非把程式碼從非併發改成併發,從單執行緒改為多執行緒,就一定能提高效能!!!
其實伴隨 多執行緒切換時的效能損耗 ,有時這些耗損累計起來將使效能遠比不使用多執行緒時的程序要來的更差。
-
-
更詳細的 Python Multi-thread 的範例分析可參考我轉譯老師本節的範例及內容中英對照的 Jupyter Notebook 筆記。