執行緒 Thread
一個行程至少會擁有一個執行緒,第一個執行緒稱為主執行緒。早期 C/C++ 要寫出多執行緒的功能,非常困難繁雜。後來Java的出現,提供了便利的方式產生多執行緒,讓程式設計師置身於幸福的世界。所以Python也不會剝奪這份幸福感。
Python對執行緒的支援提供二組標準模組,一個是低階模組 _thread,另一個是高階模組 threading。大多情況下只需使用threading即可。
Python的執行緒蠻簡單的
1. 先定義一個要使用新執行緒執行的函數。本人以Java的觀念,將此函數命名為runnable()。
2. 使用threading.Thread(目標函數, 執行緒名稱)產生一個執行緒物件 t。利用target參數將runnable丟給這個物件
3. t.start()啟動此執行緒
threading.Thread()產生物件時,若沒有給定名稱,則預設為Thread-1, Thread-2……..
threading.current_thread()會返回目前的執行緒物件,再由此物件的 name屬性即可取得目前執行緒的名稱。
在下面的代碼中,可以看到,主執行緒結束了,但新執行緒還是很努力的在執行未完成的工作。
import time, threading def runnable(): print('Starting %s' % threading.current_thread().name) for i in range(10): print('%s, i=%s' % (threading.current_thread().name, i)) time.sleep(1) print('End %s' % threading.current_thread().name) if __name__ == '__main__': print('Current thread : %s' % threading.current_thread().name) t=threading.Thread(target=runnable, name='NewThread') t.start() time.sleep(1.5) print('%s is Game Over' % threading.current_thread().name) 結果 : Current thread : MainThread Starting NewThread NewThread, i=0 NewThread, i=1 MainThread is Game Over NewThread, i=2 NewThread, i=3 NewThread, i=4 NewThread, i=5 NewThread, i=6 NewThread, i=7 NewThread, i=8 NewThread, i=9 End NewThread
任務函數參數
啟動執行緒去執行某項任務(runnable)時,若要把參數傳遞給runnable,則在調用threading.Thread()時,需把參數放在tuple中,然後指定給args
import threading
def runnable(i):
print('%s, %d' % (threading.current_thread().name,pow(i, 1/2)*10);
if __name__ == '__main__':
t1 = threading.Thread(target=runnable, name='Thread-1', args=(9,))
t1.start()
Lock
在下面代碼中,一件工作runnable,同時有二個人t1及t2 在執行,印出來的結果,有的不會換行。會發生此現象是因為t1正執行到一半時被退出,換了t2進去執行。
這現象也會因為不同電腦有不同結果。如果在速度比較慢的CPU上執行,甚至可能會發生數字重複的現象。
import threading
index = 0
def runnable():
global index
while(index<10000):
index+=1
print('%s, balance : %s' % (threading.current_thread().name, index))
if __name__ == '__main__':
t1 = threading.Thread(target=runnable, name='Thread-1')
t2 = threading.Thread(target=runnable, name='Thread-2')
t1.start()
t2.start()
結果 :
Thread-1, balance : 1
Thread-1, balance : 2
Thread-1, balance : 3
Thread-1, balance : 4
Thread-1, balance : 5
Thread-1, balance : 6
Thread-1, balance : 7
Thread-1, balance : 8
Thread-1, balance : 9Thread-2, balance : 10
Thread-2, balance : 11
Thread-2, balance : 12
Thread-2, balance : 13
Thread-2, balance : 14Thread-1, balance : 15
Thread-1, balance : 16
為了讓執行緒完整的跑完一段代碼後才允許其他執行緒進入執行,就需採用lock的機制。首先由threading取得一把鑰匙lock,然後在不允許中斷的區塊前執行lock.acquire()鎖住。並於區塊外再進行解鎖 lock.release。
import threading index = 0 lock=threading.Lock() def runnable(): global index while(index<10000): lock.acquire() try: index+=1 print('%s, balance : %s' % (threading.current_thread().name, index)) finally: lock.release() if __name__ == '__main__': t1 = threading.Thread(target=runnable, name='Thread-1') t2 = threading.Thread(target=runnable, name='Thread-2') t1.start() t2.start()
請注意,假設t1進入區塊並且鎖住後,也是有可能執行到一半被退出回等待區。此時t1是帶著鑰匙回等待區。 換t2執行進入區塊前,會因為拿不到key而強迫退出。因此這種無功而返的現象,當然會造成程式執行效能的降低。
多核CPU
Python雖是真正的多執行緒語言,但因歷史的設定中,有一個 GIL鎖(Global Interpreter Lock),造成無法啟用所有的核心來跑執行緒,只能使用一個核心來跑。
要改此問題,除了重寫Python的解譯器,否則沒有其他辦法。但重寫的工程太浩大,所以也就別再想了。