python 之 併發編程(守護線程與守護進程的區別、線程互斥鎖、死鎖現象與遞歸鎖、信號量、GIL全局解釋器鎖)

9.94 守護線程與守護進程的區別

1.對主進程來說,運行完畢指的是主進程代碼運行完畢
2.對主線程來說,運行完畢指的是主線程所在的進程內所有非守護線程統統運行完畢,主線程才算運行完畢

詳細解釋:
1.主進程在其代碼結束后就已經算運行完畢了(守護進程在此時就被回收),然後主進程會一直等非守護的子進程都運行完畢后回收子進程的資源(否則會產生殭屍進程),才會結束,
2.主線程在其他非守護線程運行完畢后才算運行完畢(守護線程在此時就被回收)。因為主線程的結束意味着進程的結束,進程整體的資源都將被回收,而進程必須保證非守護線程都運行完畢后才能結束。

9.95 線程互斥鎖

from threading import Thread,Lock
import time
mutex=Lock()
x=100
def task():
    global x
    mutex.acquire()
    temp=x
    time.sleep(0.1)
    x=temp-1
    mutex.release()
​
if __name__ == '__main__':
    start=time.time()
    t_l=[]
    for i in range(100):
        t=Thread(target=task)
        t_l.append(t)
        t.start()
    for t in t_l:
        t.join()
​
    print('',x)            #主 0
    print(time.time()-start)  #10.1311

9.96 死鎖現象與遞歸鎖

mutexA=mutexB=RLock() 一個線程拿到鎖,counter加1,該線程內又碰到加鎖的情況,則counter繼續加1,這期間所有其他線程都只能等待,等待該線程釋放所有鎖,即counter遞減到0為止

from threading import Thread,Lock,RLock
import time
# mutexA=Lock()
# mutexB=Lock()         #產生死鎖
mutexA=mutexB=RLock()    #遞歸鎖
class MyThread(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(0.1)
        mutexA.acquire()
        print('%s 拿到了A鎖' %self.name)
        mutexA.release()
        mutexB.release()
​
if __name__ == '__main__':
    for i in range(10):
        t=MyThread()
        t.start()
​
    print('')

9.97 信號量

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

# from multiprocessing import Semaphore
from threading import Thread,Semaphore,current_thread
import time,random
​
sm=Semaphore(5)
def go_wc():
    sm.acquire()
    print('%s 上廁所ing' %current_thread().getName())
    time.sleep(random.randint(1,3))
    sm.release()
​
if __name__ == '__main__':
    for i in range(23):
        t=Thread(target=go_wc)
        t.start()

與進程池是完全不同的概念,進程池Pool(4),最大隻能產生4個進程,而且從頭到尾都只是這四個進程,不會產生新的,而信號量是產生一堆線程/進程

9.10 GIL解釋器鎖

GIL:全局解釋器鎖 GIL本質就是一把互斥鎖,是夾在解釋器身上的,每一個python進程內都有這麼一把鎖,同一個進程內的所有線程都需要先搶到GIL鎖,才能執行解釋器代碼

GIL會對單進程下的多個線程造成什麼樣的影響: 多線程要想執行,首先需要爭搶GIL,對所有待執行的線程來說,GIL就相當於執行權限,同一時刻只有一個線程爭搶成功,即單進程下的多個線程同一時刻只有一個在運行,意味着單進程下的多線程沒有并行的效果,但是有併發的效果

ps:分散於不同進程內的線程不會去爭搶同一把GIL,只有同一個進程的多個線程才爭搶同一把GIL

為什麼要有GIL: Cpython解釋器的內存管理機制不是線程安全的

GIL的優缺點: 優點: 保證Cpython解釋器內存管理的線程安全

缺點: 同一進程內所有的線程同一時刻只能有一個執行,也就說Cpython解釋器的多線程無法實現并行

GIL與自定義互斥鎖的異同,多個線程爭搶GIL與自定義互斥鎖的過程分析: 相同:都是互斥鎖 不同點:GIL是加到解釋器上的,作用於全局,自定義互斥鎖作用於局部;單進程內的所有線程都會去搶GIL,單進程內的只有一部分線程會去搶自定義的互斥鎖

9.101 Cpython解釋器併發效率驗證

單進程下的多個線程是無法并行,無法并行意味着不能利用多核優勢

計算密集型應該使用多進程,如金融分析;IO密集型應該使用多線程,多核對性能的提升微不足道,如socket,爬蟲,web

9.102 線程互斥鎖與GIL對比

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

from threading import Thread,Lock
import time
mutex=Lock()
count=0
def task():
    global count
    mutex.acquire() #GIL只能讓線程先訪問到解釋器的代碼,即拿到執行權限,然後將target的代碼交給解釋器的代
    temp=count      #碼去執行,但線程拿不到mutex,同樣要等待
    time.sleep(0.1)
    count=temp+1
    mutex.release()
​
if __name__ == '__main__':
    t_l=[]
    for i in range(2):
        t=Thread(target=task)
        t_l.append(t)
        t.start()
    for t in t_l:
        t.join()
​
    print('',count)    #主   2
点赞

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *