線程(有時被稱爲輕量級進程)跟進程有些相似,不同的是所有的線程運行在同一個進程中,共享相同的運行環境。它們可以想像成是在主進程或“主線程”中並行運行的“迷你進程”。線程有開始、順序執行和結束三部分,它有一個自己的指令指針,記錄自己運行到什麼地方。線程的運行可能被搶佔(中斷)或暫時的被掛起(也叫睡眠)讓其它的線程運行,這叫做讓步。一個進程中的各個線程之間共享同一片數據空間,所以線程之間可以比進程之間更方便地共享數據以及相互通訊。線程一般都是併發執行的,正是由於這種並行和數據共享的機制使得多個任務的合作變爲可能。實際上,在單CPU的系統中,真正的併發是不可能的,每個線程會被安排成每次只運行一小會,然後就把CPU讓出來,讓其它的線程去運行。在進程的整個運行過程中,每個線程都只做自己的事,在需要的時候跟其它的線程共享運行的結果。多個線程共同訪問同一片數據不是完全沒有危險的,由於數據訪問的順序不一樣,有可能導致數據結果的不一致的問題,這叫做競態條件。而大多數線程庫都帶有一系列的同步原語,來控制線程的執行和數據的訪問。
Python全局鎖
Python代碼的執行由Python虛擬機(也叫解釋器主循環)來控制。Python在設計之初就考慮到要在主循環中,同時只有一個線程在執行。雖然 Python 解釋器中可以“運行”多個線程,但在任意時刻只有一個線程在解釋器中運行。
對Python虛擬機的訪問由全局解釋器鎖(GIL)來控制,正是這個鎖能保證同一時刻只有一個線程在運行。在多線程環境中,Python 虛擬機按以下方式執行:a、設置 GIL;b、切換到一個線程去運行;c、運行指定數量的字節碼指令或者線程主動讓出控制(可以調用 time.sleep(0));d、把線程設置爲睡眠狀態;e、解鎖 GIL;d、再次重複以上所有步驟。
任何Python線程執行前,必須先獲得GIL鎖,然後,每執行100條字節碼,解釋器就自動釋放GIL鎖,讓別的線程有機會執行。這個GIL全局鎖實際上把所有線程的執行代碼都給上了鎖,所以,多線程在Python中只能交替執行,即使100個線程跑在100核CPU上,也只能用到1個核。
例子:
1、
import threading
import time
def haha(max_num):
for i in range(max_num):
time.sleep(1)
print i
for x in range(3):
"""
這裏的rang(3)是要依次啓動三個線程,每個線程都調用函數haha()
第一個線程啓動執行之後,馬上啓動第二個線程再次執行。最後也相當於
函數執行了3次
"""
#通過threading.Thread方法實例化多線程類
#target後面跟的是函數的名稱但是不要帶括號也不填寫參數
#args後面的內容纔是要傳遞給函數haha()的參數。切記參數一定要以元組的形式填寫不然會報錯。
t=threading.Thread(target=haha,args=(10,))
#將線程設置爲守護線程
t.setDaemon(True)
#線程準備就緒,隨時等候cpu調度
t.start()
setDaemon()
如果是後臺線程,主線程執行過程中,後臺線程也在進行,主線程執行完畢後,後臺線程不論成功與否,均停止
如果是前臺線程,主線程執行過程中,前臺線程也在進行,主線程執行完畢後,等待前臺線程也執行完成後,程序停止
2、.join()的用法
import threading
import time
def haha(max_num):
for i in range(max_num):
time.sleep(1)
print i
"""
創建一個列表,用於存儲要啓動多線程的實例
"""
threads=[]
for x in range(3):
t=threading.Thread(target=haha,args=(5,))
#把多線程的實例追加入列表,要啓動幾個線程就追加幾個實例
threads.append(t)
for thr in threads:
#把列表中的實例遍歷出來後,調用start()方法以線程啓動運行
thr.start()
for thr in threads:
"""
isAlive()方法可以返回True或False,用來判斷是否還有沒有運行結束
的線程。如果有的話就讓主線程等待線程結束之後最後再結束。
"""
if thr.isAlive():
thr.join()
程序運行的時候最先啓動的一定就是主線程,主線程負責拉起子線程用於幹活。我們的例子中運行函數haha()線程其實都是子線程。因此可以說多線程其實就是多個子線程。那麼程序運行完最後一個退出的也肯定就是主線程。因此上例中最後再遍歷一個遍threads列表的目的就是查看還是否有沒有退出的子線程,只要還有子線程是活的,沒有退出。就通過join()方法強制程序流程不可以走到主線程退出的那個步驟。只有等子線程都退出之後,才能根據join()方法的規則順序執行到主線程退出的步驟。
3、通過類來實現線程
import threading
import time
class haha(threading.Thread):
"""
自定義一個類haha,必須要繼承threading.Thread,下面必須要重寫一個run()方法。
把要執行的函數寫到run()方法裏。如果沒有run()方法就會報錯。其實這個類的作用就是
通過haha類裏面的run()方法來定義每個啓動的子線程要執行的函數內容。
"""
def __init__(self,max_num):
threading.Thread.__init__(self)
self.max_num=max_num
def run(self):
for i in range(self.max_num):
time.sleep(1)
print i
if __name__==‘__main__‘:
threads=[]
for x in range(3):
"""
只是這裏和函數方式有點區別,因爲haha類繼承了threading.Thread,所以通過haha類的實例化
就相當於調用了多線程的實例化。剩下的操作就和函數方式一個樣子了。
"""
t=haha(5)
threads.append(t)
for thr in threads:
thr.start()
for thr in threads:
if thr.isAlive():
thr.join()
4、線程鎖
import threading
gnum=0
lock=threading.RLock()
def work(max_number):
for i in range(max_number):
print i
def mylock():
work(10)
#在操作gnum之前先上鎖
#acquire()的括號裏可以定義鎖定的timeout時間,超過這個時間就自動打開鎖
lock.acquire()
global gnum
gnum=gnum+1
#操作結束之後再打開鎖
lock.release()
print ‘gnum is ‘,gnum
for x in range(5):
t=threading.Thread(target=mylock)
t.start()
什麼是線程鎖呢?就是在多個線程同時操作一個資源的時候,哪個線程先操作。哪個線程就先鎖定這個資源。直到這個線程操作結束打開鎖之後,其他的線程才能再操作。這就叫做線程安全,也就是線程鎖,上例子如果不加線程鎖,得到的gnum的結果會得不到預期,線程操作是同時的,多個線程操作同一個數據。