程式和執行緒
程式是執行中的計算機程式。每個程式都擁有自己的地址空間、記憶體、資料棧及其它的輔助資料。作業系統管理著所有的程式,併為這些程式合理分配時間。程式可以通過派生新的程式來執行其它任務,不過每個程式都擁有自己的記憶體和資料棧等,程式之間的資料交換採用 程式間通訊(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.name
、Thread.ident
、Thread.daemon
。詳見(The Python Standard Library)
Thread 物件方法:
Thread.start()
、Thread.run()
、Thread.join(timeout=None)
、Thread.getName
、Thread.setName
、Thread.is_alive()
、Thread.isDaemon()
、Thread.setDaemon()
。詳見(The Python Standard Library)
使用 Thread 類,可以有很多種方法來建立執行緒,這裡使用常見的兩種:
- 建立 Thread 例項,傳給它一個函式。
- 派生 Thread 子類,並建立子類的例項。
一個單執行緒栗子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#!/usr/bin/env python3 import threading from random import randint from time import sleep, ctime def hi(n): sleep(n) print("ZzZzzz, sleep: ", n) # 列印 Sleep 的秒數 def main(): print("### Start at: ", ctime()) for i in range(10): hi(randint(1,2)) # 呼叫十次,每次 Sleep 1秒或2秒 print("### Done at: ", ctime()) if __name__ == '__main__': main() |
執行結果:
1 2 3 4 5 6 7 8 9 10 11 12 |
### Start at: Thu Sep 1 14:11:00 2016 ZzZzzz, sleep: 1 ZzZzzz, sleep: 2 ZzZzzz, sleep: 2 ZzZzzz, sleep: 2 ZzZzzz, sleep: 1 ZzZzzz, sleep: 1 ZzZzzz, sleep: 1 ZzZzzz, sleep: 1 ZzZzzz, sleep: 1 ZzZzzz, sleep: 2 ### Done at: Thu Sep 1 14:11:14 2016 |
一共是用了14秒。
多執行緒:建立 Thread 例項,傳給它一個函式
直接上程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#!/usr/bin/env python3 import threading from random import randint from time import sleep, ctime def hi(n): sleep(n) print("ZzZzzz, sleep: ", n) def main(): print("### Start at: ", ctime()) threads = [] for i in range(10): rands = randint(1,2) # 例項化每個 Thread 物件,把函式和引數傳遞進去,返回 Thread 例項 t = threading.Thread(target=hi, args=(rands,)) threads.append(t) # 分配執行緒 for i in range(10): threads[i].start() # 開始執行多執行緒 for i in range(10): threads[i].join() # (自旋鎖)等待執行緒結束或超時,然後再往下執行 print("### Done at: ", ctime()) if __name__ == '__main__': main() |
執行結果:
1 2 3 4 5 6 7 8 9 10 11 12 |
### Start at: Thu Sep 1 14:18:00 2016 ZzZzzz, sleep: 1 ZzZzzz, sleep: 1 ZzZzzz, sleep: 1 ZzZzzz, sleep: 1 ZzZzzz, sleep: 1 ZzZzzz, sleep: 1 ZzZzzz, sleep: 2 ZzZzzz, sleep: 2 ZzZzzz, sleep: 2 ZzZzzz, sleep: 2 ### Done at: Thu Sep 1 14:18:02 2016 |
使用多執行緒,只用了2秒。
多執行緒:派生 Thread 子類,並建立子類的例項
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
#!/usr/bin/env python3 import threading from random import randint from time import sleep, ctime class MyThread(threading.Thread): def __init__(self, func, args, times): super(MyThread, self).__init__() self.func = func self.args = args self.times = times def run(self): print("begin thread......", self.times) self.res = self.func(*self.args) print("end threads......", self.times) def hi(n): sleep(n) print("ZzZzzz, sleep: ", n) def main(): print("### Start at: ", ctime()) threads = [] for i in range(10): rands = randint(1,2) t = MyThread(hi, (rands,), i+1) threads.append(t) for i in range(10): threads[i].start() for i in range(10): threads[i].join() print("### Done at: ", ctime()) if __name__ == '__main__': main() |
執行結果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
### Start at: Thu Sep 1 14:47:09 2016 begin thread...... 1 begin thread...... 2 begin thread...... 3 begin thread...... 4 begin thread...... 5 begin thread...... 6 begin thread...... 7 begin thread...... 8 begin thread...... 9 begin thread...... 10 ZzZzzz, sleep: 1 ZzZzzz, sleep: 1 end threads...... 1 end threads...... 4 ZzZzzz, sleep: 1 end threads...... 7 ZzZzzz, sleep: 1 end threads...... 3 ZzZzzz, sleep: 1 end threads...... 9 ZzZzzz, sleep: 2 end threads...... 2 ZzZzzz, sleep: 2 end threads...... 5 ZzZzzz, sleep: 2 ZzZzzz, sleep: 2 end threads...... 10 end threads...... 6 ZzZzzz, sleep: 2 end threads...... 8 ### Done at: Thu Sep 1 14:47:11 2016 |
這個栗子對 Thread 子類化,而不是對其例項化,使得定製執行緒物件更具靈活性,同時也簡化執行緒建立的呼叫過程。
執行緒鎖
當多執行緒爭奪鎖時,允許第一個獲得鎖的執行緒進入臨街區,並執行程式碼。所有之後到達的執行緒將被阻塞,直到第一個執行緒執行結束,退出臨街區,並釋放鎖。需要注意,那些阻塞的執行緒是沒有順序的。
舉個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#!/usr/bin/env python3 import threading from random import randint from time import sleep, ctime L = threading.Lock() # 引入鎖 def hi(n): L.acquire() # 加鎖 for i in [1,2]: print(i) sleep(n) print("ZzZzzz, sleep: ", n) L.release() # 釋放鎖 def main(): print("### Start at: ", ctime()) threads = [] for i in range(10): rands = randint(1,2) t = threading.Thread(target=hi, args=(rands,)) threads.append(t) for i in range(10): threads[i].start() for i in range(10): threads[i].join() print("### Done at: ", ctime()) if __name__ == '__main__': main() |
執行上面的程式碼,再將鎖的程式碼註釋掉,對比下輸出。