Python 線程:簡介

已發表: 2022-10-25

在本教程中,您將學習如何使用 Python 的內置線程模塊來探索 Python 中的多線程功能。

從進程和線程的基礎知識開始,您將了解多線程在 Python 中的工作原理,同時了解並發和並行的概念。 然後,您將學習如何使用內置的threading模塊在 Python 中啟動和運行一個或多個線程。

讓我們開始吧。

進程與線程:有什麼區別?

什麼是過程?

進程是需要運行的程序的任何實例。

它可以是任何東西——Python 腳本或網絡瀏覽器(如 Chrome)到視頻會議應用程序。 如果您在計算機上啟動任務管理器並導航到Performance –> CPU ,您將能夠看到當前在您的 CPU 內核上運行的進程和線程。

cpu-proc-線程

了解進程和線程

在內部,一個進程有一個專用的內存來存儲與該進程相對應的代碼和數據。

一個進程由一個或多個線程組成。 線程是操作系統可以執行的最小指令序列,它代表了執行的流程。

每個線程都有自己的堆棧和寄存器,但沒有專用內存。 與進程關聯的所有線程都可以訪問數據。 因此,數據和內存由進程的所有線程共享。

進程和線程

在具有 N 個內核的 CPU 中,N 個進程可以在同一時間實例並行執行。 但是,同一進程的兩個線程永遠不能並行執行,但可以同時執行。 我們將在下一節討論並發與並行的概念。

根據我們目前所學的知識,讓我們總結一下進程和線程之間的區別。

特徵過程
記憶專用內存共享內存
執行方式並行,並發同時; 但不平行
執行由操作系統CPython 解釋器

Python中的多線程

在 Python 中,全局解釋器鎖 (GIL) 確保只有一個線程可以獲取鎖並在任何時間點運行。 所有線程都應該獲得這個鎖才能運行。 這確保了在任何給定時間點只能執行單個線程,並避免同時執行多線程。

例如,考慮同一進程的兩個線程t1t2 。 因為線程在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())

讓我們解析一下上面的代碼片段做了什麼:

  • 它導入threadingtime模塊。
  • 函數some_func具有描述性的print()語句,並包含一個兩秒的休眠操作: time.sleep(n)使函數休眠n秒。
  • 接下來,我們定義一個線程thread_1 ,目標為some_functhreading.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 完成執行。

線程1-ex

等待線程完成執行

如果你想讓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 。 因為startstep的默認值分別為 0 和 1,所以您可以使用range(n + 1)來獲取從 0 到 n 的序列。

接下來,我們定義兩個線程thread1和 thread2 來分別運行count_downthread2 count_up 。 我們為這兩個函數添加了print語句和sleep操作。

創建線程對象時,請注意目標函數的參數應指定為元組 - 到args參數。 由於這兩個函數( count_downcount_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_upthread2上運行,從 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

您可以看到thread1thread2交替執行,因為它們都涉及等待操作(睡眠)。 一旦count_up函數完成計數到 5, thread2就不再處於活動狀態。 所以我們得到的輸出只對應於thread1

加起來

在本教程中,您學習瞭如何使用 Python 的內置線程模塊來實現多線程。 以下是關鍵要點的摘要:

  • Thread構造函數可用於創建線程對象。 使用threading.Thread(target=<callable>,args=(<tuple of args>))創建一個線程,該線程使用args中指定的參數運行目標可調用對象。
  • Python 程序在主線程上運行,因此您創建的線程對像是附加線程。 您可以調用active_count()函數返回任何實例的活動線程數。
  • 您可以使用線程對像上的start()方法啟動一個線程,然後使用join()方法等待它完成執行。

您可以通過調整等待時間、嘗試不同的 I/O 操作等來編寫更多示例。 確保在即將到來的 Python 項目中實現多線程。 快樂編碼!