Python 多執行緒和鎖

發表於2016-09-09

程式和執行緒

程式是執行中的計算機程式。每個程式都擁有自己的地址空間、記憶體、資料棧及其它的輔助資料。作業系統管理著所有的程式,併為這些程式合理分配時間。程式可以通過派生新的程式來執行其它任務,不過每個程式都擁有自己的記憶體和資料棧等,程式之間的資料交換採用 程式間通訊(IPC) 方式。

執行緒在程式之下執行,一個程式下可以執行多個執行緒,它們之間共享相同上下文。執行緒包括開始、執行順序和結束三部分。它有一個指標,用於記錄當前執行的上下文。當其它執行緒執行時,它可以被搶佔(中斷)和臨時掛起(也稱睡眠) ——這種做法叫做 讓步(yielding)

一個程式中的各個執行緒與主程式共享同一片資料空間,與獨立程式相比,執行緒之間資訊共享和通訊更加容易。執行緒一般以併發執行,正是由於這種併發和資料共享機制,使多工間的協作成為可能。當然,這種共享也並不是沒有風險的,如果多個執行緒訪問同一資料空間,由於訪問順序不同,可能導致結果不一致,這種情況通常稱為競態條件(race condition),不過大多數執行緒庫都有同步原語,以允許執行緒管理器的控制執行和訪問;另一個要注意的問題是,執行緒無法給予公平執行時間,CPU 時間分配會傾向那些阻塞更少的函式。

全域性直譯器鎖(GIL)

Python 程式碼執行由 Python 虛擬機器 (又名直譯器主迴圈) 進行控制。Python 在設計時是這樣考慮的,在主迴圈中同時只能有一個控制執行緒在執行。對 Python 虛擬機器的訪問由 全域性直譯器(GIL) 控制,這個鎖用於,當有多個執行緒時保證同一時刻只能有一個執行緒在執行。

由於 Python 的 GIL 的限制,多執行緒更適合 I/O 密集型應用( I/O 釋放了 GIL,可以允許更多的併發),對於計算密集型應用,為了實現更好的並行性,適合使用多程式,已便利用 CPU 的多核優勢。Python 的多程式相關模組:subprocess、multiprocessing、concurrent.futures

threading 模組

threading 是 Python 高階別的多執行緒模組。

threading 模組的函式

  • active_count() 當前活動的 Thread 物件個數
  • current_thread() 返回當前 Thread 物件
  • get_ident() 返回當前執行緒
  • enumerater() 返回當前活動 Thread 物件列表
  • main_thread() 返回主 Thread 物件
  • settrace(func) 為所有執行緒設定一個 trace 函式
  • setprofile(func) 為所有執行緒設定一個 profile 函式
  • stack_size([size]) 返回新建立執行緒棧大小;或為後續建立的執行緒設定棧大小為 size
  • TIMEOUT_MAX Lock.acquire(), RLock.acquire(), Condition.wait() 允許的最大值

threading 可用物件列表:

  • Thread 表示執行執行緒的物件
  • Lock 鎖原語物件
  • RLock 可重入鎖物件,使單一程式再次獲得已持有的鎖(遞迴鎖)
  • Condition 條件變數物件,使得一個執行緒等待另一個執行緒滿足特定條件,比如改變狀態或某個值
  • Semaphore 為執行緒間共享的有限資源提供一個”計數器”,如果沒有可用資源會被阻塞
  • Event 條件變數的通用版本,任意數量的執行緒等待某個時間的發生,在改事件發生後所有執行緒被啟用
  • Timer 與 Thread 相識,不過它要在執行前等待一段時間
  • Barrier 建立一個”阻礙”,必須達到指定數量的執行緒後才可以繼續

Thread 類

Thread 物件的屬性有:Thread.nameThread.identThread.daemon。詳見(The Python Standard Library)

Thread 物件方法:
Thread.start()Thread.run()Thread.join(timeout=None)Thread.getNameThread.setNameThread.is_alive()Thread.isDaemon()Thread.setDaemon()。詳見(The Python Standard Library)

使用 Thread 類,可以有很多種方法來建立執行緒,這裡使用常見的兩種:

  • 建立 Thread 例項,傳給它一個函式。
  • 派生 Thread 子類,並建立子類的例項。

一個單執行緒栗子

執行結果:

一共是用了14秒。

多執行緒:建立 Thread 例項,傳給它一個函式

直接上程式碼:

執行結果:

使用多執行緒,只用了2秒。

多執行緒:派生 Thread 子類,並建立子類的例項

執行結果:

這個栗子對 Thread 子類化,而不是對其例項化,使得定製執行緒物件更具靈活性,同時也簡化執行緒建立的呼叫過程。

執行緒鎖

當多執行緒爭奪鎖時,允許第一個獲得鎖的執行緒進入臨街區,並執行程式碼。所有之後到達的執行緒將被阻塞,直到第一個執行緒執行結束,退出臨街區,並釋放鎖。需要注意,那些阻塞的執行緒是沒有順序的。

舉個例子:

執行上面的程式碼,再將鎖的程式碼註釋掉,對比下輸出。

相關文章