Python 線程:簡介
已發表: 2022-10-25在本教程中,您將學習如何使用 Python 的內置線程模塊來探索 Python 中的多線程功能。
從進程和線程的基礎知識開始,您將了解多線程在 Python 中的工作原理,同時了解並發和並行的概念。 然後,您將學習如何使用內置的threading
模塊在 Python 中啟動和運行一個或多個線程。
讓我們開始吧。
進程與線程:有什麼區別?
什麼是過程?
進程是需要運行的程序的任何實例。
它可以是任何東西——Python 腳本或網絡瀏覽器(如 Chrome)到視頻會議應用程序。 如果您在計算機上啟動任務管理器並導航到Performance –> CPU ,您將能夠看到當前在您的 CPU 內核上運行的進程和線程。

了解進程和線程
在內部,一個進程有一個專用的內存來存儲與該進程相對應的代碼和數據。
一個進程由一個或多個線程組成。 線程是操作系統可以執行的最小指令序列,它代表了執行的流程。
每個線程都有自己的堆棧和寄存器,但沒有專用內存。 與進程關聯的所有線程都可以訪問數據。 因此,數據和內存由進程的所有線程共享。

在具有 N 個內核的 CPU 中,N 個進程可以在同一時間實例並行執行。 但是,同一進程的兩個線程永遠不能並行執行,但可以同時執行。 我們將在下一節討論並發與並行的概念。
根據我們目前所學的知識,讓我們總結一下進程和線程之間的區別。
特徵 | 過程 | 線 |
記憶 | 專用內存 | 共享內存 |
執行方式 | 並行,並發 | 同時; 但不平行 |
執行由 | 操作系統 | CPython 解釋器 |
Python中的多線程
在 Python 中,全局解釋器鎖 (GIL) 確保只有一個線程可以獲取鎖並在任何時間點運行。 所有線程都應該獲得這個鎖才能運行。 這確保了在任何給定時間點只能執行單個線程,並避免同時執行多線程。
例如,考慮同一進程的兩個線程t1
和t2
。 因為線程在t1
讀取特定值k
時共享相同的數據,所以t2
可以修改相同的值k
。 這可能導致死鎖和不良結果。 但是只有一個線程可以獲取鎖並在任何實例上運行。 因此,GIL 也保證了線程安全。
那麼我們如何在 Python 中實現多線程能力呢? 為了理解這一點,讓我們討論並發和並行的概念。
並發與並行:概述
考慮一個具有多個內核的 CPU。 在下圖中,CPU 有四個內核。 這意味著我們可以在任何給定時刻並行運行四種不同的操作。
如果有四個進程,那麼每個進程都可以在四個核中的每一個上獨立並同時運行。 假設每個進程有兩個線程。

要了解線程的工作原理,讓我們從多核處理器架構切換到單核處理器架構。 如前所述,在特定的執行實例中只能有一個線程處於活動狀態; 但處理器內核可以在線程之間切換。

例如,I/O 綁定線程經常等待 I/O 操作:讀入用戶輸入、數據庫讀取和文件操作。 在這段等待時間內,它可以釋放鎖,以便其他線程可以運行。 等待時間也可以是簡單的操作,比如休眠n
秒。
總結:在等待操作期間,線程釋放鎖,使處理器內核能夠切換到另一個線程。 等待期結束後,較早的線程恢復執行。 這個過程,其中處理器內核同時在線程之間切換,促進了多線程。
如果您想在應用程序中實現進程級並行性,請考慮使用多處理。
Python 線程模塊:第一步
Python 附帶一個threading
模塊,您可以將其導入 Python 腳本。
import threading
要在 Python 中創建線程對象,可以使用Thread
構造函數: threading.Thread(...)
。 這是足以滿足大多數線程實現的通用語法:
threading.Thread(target=...,args=...)
這裡,
-
target
是表示 Python 可調用對象的關鍵字參數 args
是目標接受的參數元組。
您將需要 Python 3.x 來運行本教程中的代碼示例。 下載代碼並跟隨。
如何在 Python 中定義和運行線程
讓我們定義一個運行目標函數的線程。
目標函數是some_func
。
import threading import time def some_func(): print("Running some_func...") time.sleep(2) print("Finished running some_func.") thread1 = threading.Thread(target=some_func) thread1.start() print(threading.active_count())
讓我們解析一下上面的代碼片段做了什麼:
- 它導入
threading
和time
模塊。 - 函數
some_func
具有描述性的print()
語句,並包含一個兩秒的休眠操作:time.sleep(n)
使函數休眠n
秒。 - 接下來,我們定義一個線程
thread_1
,目標為some_func
。threading.Thread(target=...)
創建一個線程對象。 - 注意:指定函數的名稱而不是函數調用; 使用
some_func
而不是some_func()
。 - 創建線程對像不會啟動線程; 在線程對像上調用
start()
方法就可以了。 - 要獲取活動線程的數量,我們使用
active_count()
函數。
Python 腳本在主線程上運行,我們正在創建另一個線程 ( thread1
) 來運行函數some_func
,因此活動線程數為兩個,如輸出所示:

# Output Running some_func... 2 Finished running some_func.
如果我們仔細查看輸出,我們會看到在啟動thread1
時,第一個 print 語句運行。 但在睡眠操作期間,處理器切換到主線程並打印出活動線程的數量——無需等待thread1
1 完成執行。

等待線程完成執行
如果你想讓thread1
完成執行,你可以在啟動線程後調用它的join()
方法。 這樣做將等待thread1
完成執行而不切換到主線程。
import threading import time def some_func(): print("Running some_func...") time.sleep(2) print("Finished running some_func.") thread1 = threading.Thread(target=some_func) thread1.start() thread1.join() print(threading.active_count())
現在, thread1
在我們打印出活動線程數之前已經完成了執行。 所以只有主線程在運行,這意味著活動線程數為1。
# Output Running some_func... Finished running some_func. 1
如何在 Python 中運行多個線程
接下來,讓我們創建兩個線程來運行兩個不同的功能。
這裡, count_down
是一個函數,它接受一個數字作為參數,並從該數字倒數到零。
def count_down(n): for i in range(n,-1,-1): print(i)
我們定義count_up
,這是另一個從零計數到給定數字的 Python 函數。
def count_up(n): for i in range(n+1): print(i)
當使用具有語法
range(start, stop, step)
的range()
函數時,默認情況下會排除終點stop
。– 要從特定數字倒數到零,您可以使用負
step
值 -1 並將stop
值設置為 -1,以便包括零。– 同樣,要計數到
n
,您必須將stop
值設置為n + 1
。 因為start
和step
的默認值分別為 0 和 1,所以您可以使用range(n + 1)
來獲取從 0 到 n 的序列。
接下來,我們定義兩個線程thread1
和 thread2 來分別運行count_down
和thread2
count_up
。 我們為這兩個函數添加了print
語句和sleep
操作。
創建線程對象時,請注意目標函數的參數應指定為元組 - 到args
參數。 由於這兩個函數( count_down
和count_up
)都接受一個參數。 您必須在值之後顯式插入逗號。 這確保了參數仍然作為元組傳入,因為後續元素被推斷為None
。
import threading import time def count_down(n): for i in range(n,-1,-1): print("Running thread1....") print(i) time.sleep(1) def count_up(n): for i in range(n+1): print("Running thread2...") print(i) time.sleep(1) thread1 = threading.Thread(target=count_down,args=(10,)) thread2 = threading.Thread(target=count_up,args=(5,)) thread1.start() thread2.start()
在輸出中:
- 函數
count_up
在thread2
上運行,從 0 開始最多計數 5。 -
count_down
函數在thread1
上運行,從 10 倒數到 0。
# Output Running thread1.... 10 Running thread2... 0 Running thread1.... 9 Running thread2... 1 Running thread1.... 8 Running thread2... 2 Running thread1.... 7 Running thread2... 3 Running thread1.... 6 Running thread2... 4 Running thread1.... 5 Running thread2... 5 Running thread1.... 4 Running thread1.... 3 Running thread1.... 2 Running thread1.... 1 Running thread1.... 0
您可以看到thread1
和thread2
交替執行,因為它們都涉及等待操作(睡眠)。 一旦count_up
函數完成計數到 5, thread2
就不再處於活動狀態。 所以我們得到的輸出只對應於thread1
。
加起來
在本教程中,您學習瞭如何使用 Python 的內置線程模塊來實現多線程。 以下是關鍵要點的摘要:
- Thread構造函數可用於創建線程對象。 使用threading.Thread(target=<callable>,args=(<tuple of args>))創建一個線程,該線程使用args中指定的參數運行目標可調用對象。
- Python 程序在主線程上運行,因此您創建的線程對像是附加線程。 您可以調用active_count()函數返回任何實例的活動線程數。
- 您可以使用線程對像上的start()方法啟動一個線程,然後使用join()方法等待它完成執行。
您可以通過調整等待時間、嘗試不同的 I/O 操作等來編寫更多示例。 確保在即將到來的 Python 項目中實現多線程。 快樂編碼!