1. 程式人生 > >GIL全局解釋器鎖、死鎖遞歸鎖、信號量、Event事件、線程Queue

GIL全局解釋器鎖、死鎖遞歸鎖、信號量、Event事件、線程Queue

main 圖片 加鎖 2.0 最大 sin 解決 mutex 帶來

GIL全局解釋器鎖

  GIL本質就是一把互斥鎖,和所有互斥鎖本質一樣,都是把並發運行變成串行,以此來控制同一時間內共享數據只能被一個任務修改,進而保證數據安全

  保護不同的數據的安全,就應該加不同的鎖。例如 IO模式下的就應該用多線程 (打開文件,time.sleep,輸入輸出等等),而計算相關的就是用多進程

  在一個python的進程內,不僅有你運行的py文件的主線程還有由該主線程開啟的其他線程,還有解釋器開啟的垃圾回收等解釋器級別的線程,所有線程都運行在這一個進程內。如果多個線程的target=work 那麽執行流程是 多個線程先訪問到解釋器的代碼,即拿到執行權限,然後將target的代碼交給解釋器的代碼去執行,解釋器的代碼是所有線程共享的,所以垃圾回收線程也可能訪問到解釋器的代碼而去執行,這就導致了一個問題:對於同一個數據100,可能線程1執行x=100的同時,而垃圾回收執行的是回收100的操作,解決這種問題沒有什麽高明的方法就是加鎖處理,如下圖,保證python解釋器同一時間只能執行一個任務的代碼

技術分享圖片

GIL保護的是解釋器級的數據,保護用戶自己的數據則需要自己加鎖處理如下圖

技術分享圖片

有了GIL的存在,同一時刻同一進城只有一個線程被執行

對於計算來說,cpu越多越好,但是對於I/O來說 再多cpu也沒用

我們有四個任務需要處理,處理方式肯定是要玩出並發的效果,解決方案可以是:

方案一:開啟四個進程

方案二:一個進程下,開啟四個線程

單核情況下:如果四個任務是計算密集型,沒有多核來並行計算,方案一徒增了創建進程的開銷,方案二勝   如果四個任務是I/O密集型,方案一創建進程的開銷大,且進程的切換速度遠不如線程,方案二勝

多核情況下:如果四個任務是計算密集型,多核意味著並行計算,在python中一個進程中同一時刻只有一個線程執行用不上多核,方案一勝   如果四個任務是I/O密集型,再多的核也解決不了I/O問題,方案二勝

結論:現在的計算機基本上都是多核,python對於計算密集型的任務開多線程的效率並不能帶來多大性能上的提升,甚至不如串行(沒有大量切換),但是,對於IO密集型的任務效率還是有顯著提升的。

# 計算密集型:應該使用多進程
from multiprocessing import Process
from threading import Thread
import os,time

def work():
    res=0
    for i in range(100000000):
        res*=i

if __name__ == ‘__main__‘:
    l=[]
    start=time.time()
    for i in range(6):
        p=Process(target=work)#進程用時18.202494621276855
        # p=Thread(target=work)#進程用時32.08451318740845
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print(‘run time is %s‘ %(stop-start))

# IO密集型: 應該開啟多線程
# from threading import Thread
# from multiprocessing import  Process
# import time,random
#
# def task():
#     time.sleep(2)
#
# if __name__ == ‘__main__‘:
#     l=[]
#     star=time.time()
#     for i in range(10):
#         t=Process(target=task) #進程用時3.76550555229187
#         t=Thread(target=task) #線程用時2.002195119857788
#         l.append(t)
#         t.start()
#     for n in l:
#         n.join()
#     stop=time.time()
#     print(stop-star)

死鎖與遞歸鎖

死鎖就是指兩個或兩個以上的進程或線程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,他們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程就稱為死鎖進程

 

from threading import Thread,Lock,RLock
import time
mutexA=Lock()
mutexB=Lock()
# 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()
‘‘‘
Thread-1 拿到A鎖
Thread-1 拿到B鎖
Thread-1 拿到B鎖
Thread-2 拿到A鎖
然後就卡住,死鎖了
‘‘‘

解決方法就是遞歸鎖,就是RLock() 遞歸鎖就是一個線程或者進程 搶到鎖後 可以多次拿這個鎖,而這個鎖每被拿一次他內部就會記錄一次 ,除非一個進程或者線程所有的acquire 都被 release 其他線程才能去爭搶鎖

信號量 Semaphore

同進程一樣

Semaphore管理一個內置的計數器,每當調用acquire()時計數器-1 調用release時計數器+1 計數器不能小於0,當計數器為0時acquire將阻塞線程直到其他線程調用release

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()

這段代碼內Semaphors設置了最大數量5 剛開始進去5個線程 當有兩個線程調用release 時 其他的線程就可以再acquire兩個。

Event事件

  線程的一個關鍵特性是每個線程都是獨立運行且狀態不可預測。如果程序中的其 他線程需要通過判斷某個線程的狀態來確定自己下一步的操作,這時線程同步問題就會變得非常棘手。為了解決這些問題,我們需要使用threading庫中的Event對象。 對象包含一個可由線程設置的信號標誌,它允許線程等待某些事件的發生。在 初始情況下,Event對象中的信號標誌被設置為假。如果有線程等待一個Event對象, 而這個Event對象的標誌為假,那麽這個線程將會被一直阻塞直至該標誌為真。一個線程如果將一個Event對象的信號標誌設置為真,它將喚醒所有等待這個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()

線程Queue

  和進程Queue一樣

import queue

# queue.Queue() #先進先出
q=queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())

# queue.LifoQueue() #後進先出->堆棧
q=queue.LifoQueue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())

# queue.PriorityQueue() #優先級
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())

GIL全局解釋器鎖、死鎖遞歸鎖、信號量、Event事件、線程Queue