多執行緒

      在〈多執行緒〉中尚無留言

執行緒 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的解譯器,否則沒有其他辦法。但重寫的工程太浩大,所以也就別再想了。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *