python學習之執行緒
執行緒
什麼是執行緒?
在一個程式中,可獨立執行的程式片段叫作“執行緒”(Thread),利用它程式設計的概念就叫作“多執行緒處理”。多執行緒是為了同步完成多項任務,不是為了提高執行效率,而是為了提高資源使用效率來提高系統的效率(執行緒之間可以共享記憶體和變數,資源消耗少)。執行緒是在同一時間需要完成多項任務的時候實現的。
執行緒的優劣
- 優勢:執行緒之間可共享記憶體和變數,資源消耗少
- 不足:執行緒之間的同步和加鎖較麻煩
threading庫
在python中主要是通過thread和threading兩個標準塊實現,thread是低階版本,threading是更高階的版本,一般來說都是使用threading這個模組,以下
基礎款
一個程序有至少有一個執行緒,因此任何程序預設就會啟動一個執行緒,此為主執行緒,主執行緒又可啟動新的執行緒,Python的threading模組有個current_thread()函式,它永遠返回當前執行緒的例項。主執行緒例項的名字叫MainThread,子執行緒的名字在建立時指定,而名字僅僅在列印時用來顯示,完全沒有其他意義,如果不起名字Python就自動給執行緒命名為Thread-1,Thread-2……
import threading
def my_print():
print 'thread %s is running...' %threading.current_thread().name
for i in range(3):
print '%s >>> %d' %(threading.current_thread().name, i)
print 'thread %s is running...' %threading.current_thread().name
thread1 = threading.Thread(target=my_print, name='First')
thread1.start()
thread1.join() # 等待子執行緒處理結束後,再執行main程式碼
print 'thread %s is ending' %threading.current_thread().name
# 結果
thread MainThread is running...
thread First is running...
First >>> 0
First >>> 1
First >>> 2
thread MainThread is ending
多執行緒
上述程式碼是執行緒啟動一個基礎版,但是執行緒啟動,肯定不是一個執行緒就可滿足龐大的需求,因此,多執行緒是必不可少的,以下
import threading
base = 1
def inc():
global base
base += 1
def run_thread():
for i in range(1000):
inc()
thread1 = threading.Thread(target=run_thread, name='t1')
thread2 = threading.Thread(target=run_thread, name='t2')
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print base
上面的程式碼就是一個簡單的多執行緒實現版本,那麼大家有沒有想過程式碼的輸出結果是怎樣的?以下,我進行了n此實驗,結果如下:
1713
1796
1583
1707
1636
2001
1723
1804
1921
...
對應結果,發現問題沒,正常來說,結果應是2001,但是運行了這麼多次,只出現一次2001,其他都比2001小,這是為什麼呢?
回顧一下,執行緒之間是共享記憶體的,那麼免不了記憶體競爭,那麼上面程式碼到底在哪塊發生了競爭,我們一步步進行分析。
- base是一個全域性變數,在inc()函式中發生了base += 1,如下
def inc():
global base
base += 1
- base += 1進一步分解,如下
temp = base + 1
base = temp
- 這樣就發現了問題所在,temo為區域性變數,每個執行緒都有自己的temp,程式碼正常執行時
temp1 = base+1 # temp1=2
base = temp1 # base=2
temp2 = base+1 # temp2=3
base = temp2 # base=3
# 結果
base = 3
- 但是執行緒去共享資源,因此可能就會發生以下的操作
temp1 = base+1 # temp1=2
temp2 = base+1 # temp2=2
base = temp1 # base=2
base = temp2 # base=2
# 結果
base = 2
- 綜上,要修改base時,需要多條語句聯合完成,而執行這幾條語句時,執行緒可能終端,導致多個執行緒將一個物件的內容改亂,因此就會出現上述的結果
加鎖版
當然,我們肯定不希望數字這樣變化,試想一下,去銀行存錢時,存錢的過程中,錢缺被別人取走了,是什麼樣的體驗,為此,必須保證修改base的時候,別的執行緒一定不能發生變動和修改。
因此,如果要確保base計算正確,就需要給inc()加上一把鎖,當某個執行緒開始執行時inc()時,可以說該執行緒獲得了鎖,其他執行緒則不能同時執行inc(),只能等到鎖被釋放後,其他執行緒獲取鎖才可完成修改。而這個鎖只有一把,因此同一時刻也只有一個執行緒持有該鎖,便不會造成修改衝突。python的鎖是通過threading.Lock()實現。
import threading
base = 1
lock = threading.Lock()
def inc():
global base
base += 1
def run_thread():
for i in range(1000):
lock.acquire()
try:
inc()
finally:
lock.release()
thread1 = threading.Thread(target=run_thread, name='t1')
thread2 = threading.Thread(target=run_thread, name='t2')
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print base
# 結果
2001
以上,便是多執行緒加鎖的例項,而獲得鎖的執行緒用完後一定要釋放鎖,否則那些苦苦等待鎖的執行緒將永遠等待下去,成為死執行緒。因此用try…finally來確保鎖一定會被釋放