1. 程式人生 > >python學習之執行緒

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來確保鎖一定會被釋放