1. 程式人生 > >Python入門學習-DAY36-GIL全局解釋器鎖、死鎖現象與遞歸鎖、信號量、Event事件、線程queue

Python入門學習-DAY36-GIL全局解釋器鎖、死鎖現象與遞歸鎖、信號量、Event事件、線程queue

可重入 def 代碼 threading 結果 運算 分析 rand pen

一、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 Process
from threading import Thread import os,time def task(): res=0 for i in range(100000000): res*=i if __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 Code

I/O密集型

技術分享圖片
from multiprocessing import Process
from threading import Thread
import os,time

def 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,Lock
import time

mutexA=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,RLock
import time

mutexB=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,Semaphore
import time,random
sm=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,Event
import time

event=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 queue
q=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 queue
q=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 queue
q=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