有些時候,比如下載圖片,因為下載圖片是一個耗時的操作。如果采用之前那種同步的方式下載。那效率肯會特別慢。這時候我們就可以考慮使用多線程的方式來下載圖片。
多線程介紹:
多線程是為了同步完成多項任務(wù),通過提高資源使用效率來提高系統(tǒng)的效率。線程是在同一時間需要完成多項任務(wù)的時候?qū)崿F(xiàn)的。
最簡單的比喻多線程就像火車的每一節(jié)車廂,而進程則是火車。車廂離開火車是無法跑動的,同理火車也可以有多節(jié)車廂。多線程的出現(xiàn)就是為了提高效率。同時它的出現(xiàn)也帶來了一些問題。
threading模塊介紹:
threading模塊是Python中專門提供用來做多線程編程的模塊。threading模塊中最常用的類是Thread。以下看一個簡單的多線程程序:
查看線程數(shù):
使用threading.enumerate()函數(shù)便可以看到當(dāng)前線程的數(shù)量。
查看當(dāng)前線程的名字:
使用threading.current_thread()可以看到當(dāng)前線程的信息。
繼承自threading.Thread類:
為了讓線程代碼更好的封裝。可以使用threading模塊下的Thread類,繼承自這個類,然后實現(xiàn)run方法,線程就會自動運行run方法中的代碼。示例代碼如下:
多線程共享全局變量的問題:
多線程都是在同一個進程中運行的。因此在進程中的全局變量所有線程都是可共享的。這就造成了一個問題,因為線程執(zhí)行的順序是無序的。有可能會造成數(shù)據(jù)錯誤。比如以下代碼:
以上結(jié)果正常來講應(yīng)該是6,但是因為多線程運行的不確定性。因此最后的結(jié)果可能是隨機的。
鎖機制:
為了解決以上使用共享全局變量的問題。threading提供了一個Lock類,這個類可以在某個線程訪問某個變量的時候加鎖,其他線程此時就不能進來,直到當(dāng)前線程處理完后,把鎖釋放了,其他線程才能進來處理。示例代碼如下:
Lock版本生產(chǎn)者和消費者模式:
生產(chǎn)者和消費者模式是多線程開發(fā)中經(jīng)常見到的一種模式。生產(chǎn)者的線程專門用來生產(chǎn)一些數(shù)據(jù),然后存放到一個中間的變量中。消費者再從這個中間的變量中取出數(shù)據(jù)進行消費。但是因為要使用中間變量,中間變量經(jīng)常是一些全局變量,因此需要使用鎖來保證數(shù)據(jù)完整性。以下是使用threading.Lock鎖實現(xiàn)的“生產(chǎn)者與消費者模式”的一個例子:
Condition版的生產(chǎn)者與消費者模式:
Lock版本的生產(chǎn)者與消費者模式可以正常的運行。但是存在一個不足,在消費者中,總是通過while True死循環(huán)并且上鎖的方式去判斷錢夠不夠。上鎖是一個很耗費CPU資源的行為。因此這種方式不是最好的。還有一種更好的方式便是使用threading.Condition來實現(xiàn)。threading.Condition可以在沒有數(shù)據(jù)的時候處于阻塞等待狀態(tài)。一旦有合適的數(shù)據(jù)了,還可以使用notify相關(guān)的函數(shù)來通知其他處于等待狀態(tài)的線程。這樣就可以不用做一些無用的上鎖和解鎖的操作。可以提高程序的性能。首先對threading.Condition相關(guān)的函數(shù)做個介紹,threading.Condition類似threading.Lock,可以在修改全局?jǐn)?shù)據(jù)的時候進行上鎖,也可以在修改完畢后進行解鎖。以下將一些常用的函數(shù)做個簡單的介紹:
1、acquire:上鎖。
2、release:解鎖。
3、wait:將當(dāng)前線程處于等待狀態(tài),并且會釋放鎖。可以被其他線程使用notify和notify_all函數(shù)喚醒。被喚醒后會繼續(xù)等待上鎖,上鎖后繼續(xù)執(zhí)行下面的代碼。
4、notify:通知某個正在等待的線程,默認(rèn)是第1個等待的線程。
5、notify_all:通知所有正在等待的線程。notify和notify_all不會釋放鎖。并且需要在release之前調(diào)用。
Condition版的生產(chǎn)者與消費者模式代碼如下:
Queue線程安全隊列:
在線程中,訪問一些全局變量,加鎖是一個經(jīng)常的過程。如果你是想把一些數(shù)據(jù)存儲到某個隊列中,那么Python內(nèi)置了一個線程安全的模塊叫做queue模塊。Python中的queue模塊中提供了同步的、線程安全的隊列類,包括FIFO(先進先出)隊列Queue,LIFO(后入先出)隊列LifoQueue。這些隊列都實現(xiàn)了鎖原語(可以理解為原子操作,即要么不做,要么都做完),能夠在多線程中直接使用。可以使用隊列來實現(xiàn)線程間的同步。相關(guān)的函數(shù)如下:
初始化Queue(maxsize):創(chuàng)建一個先進先出的隊列。
1、qsize():返回隊列的大小。
2、empty():判斷隊列是否為空。
3、full():判斷隊列是否滿了。
4、get():從隊列中取最后一個數(shù)據(jù)。
5、put():將一個數(shù)據(jù)放到隊列中。
使用生產(chǎn)者與消費者模式多線程下載表情包:
GIL全局解釋器鎖:
Python自帶的解釋器是CPython。CPython解釋器的多線程實際上是一個假的多線程(在多核CPU中,只能利用一核,不能利用多核)。同一時刻只有一個線程在執(zhí)行,為了保證同一時刻只有一個線程在執(zhí)行,在CPython解釋器中有一個東西叫做GIL(Global Intepreter Lock),叫做全局解釋器鎖。這個解釋器鎖是有必要的。因為CPython解釋器的內(nèi)存管理不是線程安全的。當(dāng)然除了CPython解釋器,還有其他的解釋器,有些解釋器是沒有GIL鎖的,見下面:
1、Jython:用JAVA實現(xiàn)的Python解釋器。不存在GIL鎖。
2、IronPython:用.net實現(xiàn)的Python解釋器。不存在GIL鎖。
3、PyPy:用Python實現(xiàn)的Python解釋器。存在GIL鎖。
GIL雖然是一個假的多線程。但是在處理一些IO操作(比如文件讀寫和網(wǎng)絡(luò)請求)還是可以在很大程度上提高效率的。在IO操作上建議使用多線程提高效率。在一些CPU計算操作上不建議使用多線程,而建議使用多進程。
多線程下載百思不得姐段子作業(yè):






