Python中的多線程[帶有編碼示例]

已發表: 2020-11-30

掌握 Python 基礎知識後,下一步就是改進和加快代碼速度。 多線程是使用“線程”實現優化的一種方式。 這些線程是什麼? 這些與流程有何不同? 讓我們來了解一下。

在本教程結束時,您將掌握以下知識:

  • 什麼是線程和進程?
  • 多線程是如何實現的?
  • 它的局限性是什麼?
  • 何時使用多線程?

學習世界頂尖大學的在線數據科學課程獲得行政 PG 課程、高級證書課程或碩士課程,以加快您的職業生涯。

目錄

Python 中的線程

當我們想到多任務時,我們會想到並行執行。 多線程不是嚴格的並行執行。 線程可以被認為是獨立運行的程序不同部分的執行流的獨立實體。 所以本質上,線程不是並行執行的,但是 Python 從一個線程切換到另一個線程的速度如此之快,以至於它們看起來是並行的。

另一方面,進程是嚴格並行的,並在不同的內核上運行以實現更快的執行。 線程也可以在不同的處理器上運行,但從技術上講,它們仍​​然不會並行運行。

您是否在考慮如果線程不並行運行,那麼它們如何使事情變得更快? 答案是它們並不總是使處理速度更快。 多線程專門用於線程將加快處理速度的任務。

線程的所有信息都包含在線程控制塊(TCB)中。 TCB由以下主要部分組成:

  1. 唯一的 TID – 線程標識符
  2. 堆棧指針,指向進程中線程的堆棧
  3. 程序計數器,存儲線程當前正在執行的指令的地址
  4. 線程狀態(運行、就緒、等待、開始或完成)

話雖如此,進程中可以包含多個線程,它們共享代碼、數據和所有文件。 並且所有線程都有自己獨立的寄存器和堆棧,它們可以訪問。

現在您可能想知道,如果線程使用公共數據和代碼,它們如何在不妨礙其他線程的情況下都使用它。 這是我們將在本教程後面討論的多線程的最大限制。

上下文切換

現在如上所述,線程不會並行運行,而是並行運行。 所以當一個線程 T1 開始執行時,所有其他線程都處於等待模式。 只有在 T1 完成執行後,任何其他排隊線程才能開始執行。 Python 從一個線程切換到另一個線程的速度如此之快,以至於看起來像是並行執行。 這種切換就是我們所說的“上下文切換”。

多線程編程

考慮下面的代碼,它使用線程來執行立方體和平方運算。

導入線程

def立方(n) :
打印( “立方體:{}” .format(n * n * n))

def平方(n)
打印( “正方形:{}” .format(n * n))

如果__name__ == “__main__”
# 創建線程
t1 = threading.Thread(target=squarer, args=( 5 ,))
t2 = threading.Thread(target=cuber, args=( 5 ,))

# 啟動線程 t1
t1.start()
# 啟動線程 t2
t2.start()

# 等到t1完成
t1.join()
# 等到t2完成
t2.join()

# 兩個線程都完成了
打印( “完成!”

#輸出:
廣場: 25
立方體: 125
完畢!

現在讓我們嘗試理解代碼。

首先,我們導入負責所有任務的線程模塊。 在 main 內部,我們通過創建 Thread 類的子類來創建 2 個線程。 我們需要傳遞目標,即需要在該線程中執行的函數,以及需要傳遞給這些函數的參數。

現在一旦聲明了線程,我們就需要啟動它們。 這是通過在線程上調用start方法來完成的。 一旦啟動,主程序需要等待線程完成它們的處理。 我們使用wait方法讓主程序暫停並等待線程 T1 和 T2 完成它們的執行。

必讀:初學者的 Python 挑戰

線程同步

正如我們上面所討論的,線程不會並行執行,而是 Python 從一個切換到另一個。 因此,線程之間的正確同步非常關鍵,以避免任何奇怪的行為。

比賽條件

同一進程下的線程使用公共數據和文件,這可能導致多個線程之間的數據“競爭”。 因此,如果一條數據被多個線程訪問,它會被兩個線程都修改,我們得到的結果不會像預期的那樣。 這稱為競爭條件。

因此,如果您有兩個線程可以訪問相同的數據,那麼它們都可以在該特定線程執行時訪問和修改它。 所以當 T1 開始執行並修改一些數據時,T2 處於睡眠/等待模式。 然後 T1 停止執行並進入睡眠模式,將控制權交給 T2,T2 也可以訪問相同的數據。 所以 T2 現在將修改和覆蓋相同的數據,這將在 T1 再次開始時導致問題。

線程同步的目的是確保這種競爭條件永遠不會出現,並且代碼的關鍵部分由線程以同步方式一次訪問。

鎖具

為了解決和防止 Race Condition 及其後果,thread 模塊提供了一個Lock類,它使用 Semaphores 來幫助線程同步。 信號量只不過是二進制標誌。 將它們視為電話亭上的“參與”標誌,其值為“參與”(等於 1)或“未參與”(等於 0)。 所以每次線程遇到帶鎖的代碼段時,它必須檢查鎖是否已經處於 1 狀態。 如果是,那麼它必須等到它變為 0 才能使用它。

Lock有兩個主要方法:

  1. acquire([blocking]) acquire方法接受參數阻塞作為TrueFalse 如果線程 T1 的鎖以阻塞為 True 啟動,它將等待或保持阻塞,直到代碼的關鍵部分被另一個線程 T2 鎖定。 一旦另一個線程 T2 釋放鎖,線程 T1 獲取鎖並返回True

另一方面,如果線程 T1 的鎖定是在參數阻塞為False的情況下啟動的,如果臨界區已經被線程 T2 鎖定,線程 T1 將不會等待或保持阻塞。 如果它認為它已鎖定,它將直接返回 False 並退出。 但是,如果代碼沒有被另一個線程鎖定,它將獲取鎖定並返回True

release() :當在鎖上調用 release 方法時,它會解鎖鎖並返回 True。 此外,它還將檢查是否有任何線程正在等待釋放鎖。 如果有,那麼它將允許其中一個人訪問鎖。

但是,如果鎖已解鎖,則會引發 ThreadError。

死鎖

當我們處理多個鎖時出現的另一個問題是——死鎖。 當線程由於各種原因沒有釋放鎖時,就會發生死鎖。 讓我們考慮一個簡單的示例,我們執行以下操作:

導入線程

l = threading.Lock()
# 第一次獲取之前
l.acquire()
# 在第二次獲取之前
l.acquire()
# 現在獲得了兩次鎖

在上面的代碼中,我們調用了兩次acquire方法,但在第一次獲取後不釋放它。 因此,當 Python 看到第二個獲取語句時,它將無限期地進入等待模式,因為我們從未釋放過前一個鎖。

這些死鎖條件可能會在您沒有意識到的情況下潛入您的代碼中。 即使您包含釋放調用,您的代碼也可能在中途失敗,並且永遠不會調用釋放並且鎖將保持鎖定狀態。 克服這個問題的一種方法是使用with as語句,也稱為上下文管理器。 使用with - as語句,一旦處理結束或由於任何原因失敗,鎖將自動釋放。

閱讀: Python 項目理念和主題

在你走之前

正如我們之前所討論的,多線程並不是在所有應用程序中都有用,因為它並沒有真正讓事情並行運行。 但是多線程的主要應用是在 I/O 任務期間,CPU 在等待數據加載時處於空閒狀態。 多線程在這裡起著至關重要的作用,因為 CPU 的空閒時間被用於其他任務,從而使其成為優化的理想選擇。

如果您想了解數據科學,請查看 IIIT-B 和 upGrad 的數據科學執行 PG 計劃,該計劃是為在職專業人士創建的,提供 10 多個案例研究和項目、實用的實踐研討會、行業專家的指導、1與行業導師一對一,400 多個小時的學習和頂級公司的工作協助。

Python中的線程是什麼?

線程是進程中的實體,可以安排在 Python 中執行。 通俗的講,線程是計算機執行的計算過程。 它是程序中的一組此類指令,開發人員可以獨立於其他腳本運行。 線程允許您通過使用並行性來提高應用程序速度。 這是一個輕量級進程,可以讓任務並行運行。 線程獨立運行並最大限度地利用 CPU,從而提高 CPU 性能。

Python中的多線程有什麼用?

多線程是 Python 編程中的一種線程技術,它允許多個線程在 CPU 的幫助下通過線程之間的快速切換(稱為上下文切換)來並發操作。 當我們可以將任務分成多個單獨的部分時,我們使用多線程。 例如,假設您需要執行複雜的數據庫查詢以獲取數據並將該查詢分解為多個單獨的查詢。 在這種情況下,最好為每個查詢分配一個線程並並行運行它們。

什麼是線程同步?

線程同步被描述為一種保證兩個或多個並發進程或線程不會同時執行程序的關鍵部分的方法。 同步方法用於控制進程對重要部分的訪問。 當我們在一個程序中啟動兩個或多個線程時,有可能多個線程可能會嘗試訪問同一個資源,從而由於並發挑戰而導致意外結果。 例如,如果許多線程嘗試在同一個文件中寫入,則數據可能會損壞,因為其中一個線程可以覆蓋數據,或者當一個線程正在打開而另一個線程正在關閉同一個文件時。