標籤:如何 進入 解譯器 color 不能 target iss rgs 方式
一、GIL全域解譯器鎖1. 什麼是GIL全域解譯器鎖
GIL本質就是一把互斥鎖,相當於執行許可權
在Cpython解譯器下,如果想實現並行可以開啟多個進程
2. 為何要有GIL
我們首先要知道,一個多線程是怎麼執行的,假設在一個進程中有三個線程,線程中是要啟動並執行代碼。
①如果要運行代碼,就必須要先獲得Cpython解譯器的許可權才能將代碼交由解譯器翻譯成cpu可以理解的語言
②再將翻譯好的代碼交由作業系統,由作業系統交給CPU執行運算。
由於每個進程內都會存在一把GIL,同一進程內的多個線程,必須搶到GIL之後才能使用Cpython解譯器來執行自己的代碼
即同一進程下的多個線程無法實現並行,但是可以實現並發
那麼我們反過來想一下,如果沒有GIL的存在,那麼多個線程就變成了並行的,要知道解譯器中有一個記憶體回收機制,其實也是一個線程,也變成了並行,就會造成一種情況的發生,對於同一個資料100,可能線程1執行x=100的同時,而記憶體回收執行的是回收100的操作,造成了資料的丟失。
為了Cpython解譯器的記憶體回收機制的安全執行緒,就必須使用GIL
3. 如何用GIL
有了GIL,應該如何處理並發
我們有四個任務需要處理,處理方式肯定是要玩出並發的效果,解決方案可以是:
方案一:開啟四個進程
方案二:一個進程下,開啟四個線程
單核情況下,分析結果:
如果四個任務是計算密集型,沒有多核來並行計算,方案一徒增了建立進程的開銷,方案二勝
如果四個任務是I/O密集型,方案一建立進程的開銷大,且進程的切換速度遠不如線程,方案二勝
多核情況下,分析結果:
如果四個任務是計算密集型,多核意味著並行計算,在python中一個進程中同一時刻只有一個線程執行用不上多核,方案一勝
如果四個任務是I/O密集型,再多的核也解決不了I/O問題,方案二勝
結論:現在的電腦基本上都是多核,python對於計算密集型的任務開多線程的效率並不能帶來多大效能上的提升,甚至不如串列(沒有大量切換),但是,對於IO密集型的任務效率還是有顯著提升的。
計算密集型
from multiprocessing import Processfrom threading import Threadimport os,timedef task(): res=0 for i in range(100000000): res*=iif __name__ == ‘__main__‘: l=[] print(os.cpu_count()) #本機為4核 start=time.time() for i in range(4): # p=Process(target=task) #耗時16.226743459701538s p=Thread(target=task) #耗時26.44382882118225s l.append(p) p.start() for p in l: p.join() stop=time.time() print(‘run time is %s‘ %(stop-start))
View CodeI/O密集型
from multiprocessing import Processfrom threading import Threadimport os,timedef task(): time.sleep(2)if __name__ == ‘__main__‘: l=[] print(os.cpu_count()) #本機為4核 start=time.time() for i in range(400): # p=Process(target=task) #耗時29.650749683380127s p=Thread(target=task) #耗時2.0773582458496094s l.append(p) p.start() for p in l: p.join() stop=time.time() print(‘run time is %s‘ %(stop-start))
View Code
二、死結現象與遞迴鎖死結現象
就是線程1拿到線程2需要的那把鎖,線程2拿著線程1需要的那把鎖,雙方拿著對方需要的資源,但是雙方都無法釋放
就好比我我被鎖在這個房間裡,我的手裡拿著隔壁房間的鑰匙,而另一個人被鎖在隔壁的房間,他手裡拿著我這個房間的鑰匙,我們都需要對方的鑰匙,但是都被鎖住了
這就是死結,是指兩個或兩個以上的進程或線程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。
from threading import Thread,Lockimport timemutexA=Lock()mutexB=Lock()class Mythead(Thread): def run(self): self.f1() self.f2() def f1(self): mutexA.acquire() print(‘%s 搶到A鎖‘ %self.name) mutexB.acquire() print(‘%s 搶到B鎖‘ %self.name) mutexB.release() mutexA.release() def f2(self): mutexB.acquire() print(‘%s 搶到了B鎖‘ %self.name) time.sleep(2) mutexA.acquire() print(‘%s 搶到了A鎖‘ %self.name) mutexA.release() mutexB.release()if __name__ == ‘__main__‘: for i in range(100): t=Mythead() t.start()
View Code遞迴鎖
在Python中為了支援在同一線程中多次請求同一資源,python提供了可重新進入鎖RLock。
這個RLock內部維護著一個Lock和一個counter變數,counter記錄了acquire的次數,從而使得資源可以被多次require。直到一個線程所有的acquire都被release,其他的線程才能獲得資源。上面的例子如果使用RLock代替Lock,則不會發生死結
from threading import Thread,Lock,RLockimport timemutexB=mutexA=RLock()class Mythead(Thread): def run(self): self.f1() self.f2() def f1(self): mutexA.acquire() print(‘%s 搶到A鎖‘ %self.name) mutexB.acquire() print(‘%s 搶到B鎖‘ %self.name) mutexB.release() mutexA.release() def f2(self): mutexB.acquire() print(‘%s 搶到了B鎖‘ %self.name) time.sleep(2) mutexA.acquire() print(‘%s 搶到了A鎖‘ %self.name) mutexA.release() mutexB.release()if __name__ == ‘__main__‘: for i in range(100): t=Mythead() t.start()
View Code
三、訊號量
訊號量Semaphore是同時允許一定數量的線程更改資料 ,比如廁所有5個坑,那最多隻允許5個人上廁所,後面的人只能等裡面有人出來了才能再進去,如果指定訊號量為5,那麼來一個人獲得一把鎖,計數加1,當計數等於5時,後面的人均需要等待。一旦釋放,就有人可以獲得一把鎖
from threading import Thread,Semaphoreimport time,randomsm=Semaphore(5)def task(name): sm.acquire() print(‘%s 正在上廁所‘ %name) time.sleep(random.randint(1,3)) sm.release()if __name__ == ‘__main__‘: for i in range(20): t=Thread(target=task,args=(‘路人%s‘ %i,)) t.start()
View Code
四、Event事件
當一個線程需要根據另一個線程才能判斷是否執行,比如紅綠燈路口,車輛是否能行駛,就依靠紅綠燈給出的資訊才行駛。為瞭解決這個問題就需要Event事件
event.isSet():返回event的狀態值;
event.wait():如果 event.isSet()==False將阻塞線程;
event.set(): 設定event的狀態值為True,所有阻塞池的線程啟用進入就緒狀態, 等待作業系統調度;
event.clear():恢複event的狀態值為False。
from threading import Thread,Eventimport timeevent=Event()def light(): print(‘紅燈正亮著‘) time.sleep(3) event.set() #綠燈亮def car(name): print(‘車%s正在等綠燈‘ %name) event.wait() #等燈綠 print(‘車%s通行‘ %name)if __name__ == ‘__main__‘: # 紅綠燈 t1=Thread(target=light) t1.start() # 車 for i in range(10): t=Thread(target=car,args=(i,)) t.start()
View Code
五、線程queue
先進先出queue.Queue()
import queueq=queue.Queue(3)q.put(1)q.put(2)q.put(3)print(q.get())print(q.get())print(q.get())
View Code後進先出->堆棧queue.LifoQueue()
import queueq=queue.LifoQueue(3)q.put(1)q.put(2)q.put(3)print(q.get())print(q.get())print(q.get())
View Code優先順序queue.PriorityQueue()
import queueq=queue.PriorityQueue(3) #優先順序,優先順序用數字表示,數字越小優先順序越高q.put((10,‘a‘))q.put((-1,‘b‘))q.put((100,‘c‘))print(q.get())print(q.get())print(q.get())
View Code
Python入門學習-DAY36-GIL全域解譯器鎖、死結現象與遞迴鎖、訊號量、Event事件、線程queue