在Python中GIL解釋器鎖、同步鎖、死鎖、遞歸鎖都是什麼?怎麼這麼多鎖,它們都是用來控制進程、線程的嗎?作為一個程序猿,你真的理解並行與並發,同步與異步了麼?
希望本篇文章能給你想要的答案…
1、並發
一個系統具有處理多個任務的能力,一個CPU可以處理多個任務也叫並發,因為CPU的切換速度極其快
2、並行
一個系統具有 同時 處理多個任務的能力,同時是嚴格意義上的同時進行
3、區別: 因為一個CPU是可以不斷切換任務的,可以實現並發效果,但一個CPU某一個微小的時刻只能正在處理一個任務。
因此,多核的CPU才具有並行的能力,並行是並發的一個子集,並行一定能並發
4、測試代碼:
import threading
import time
#定義累加和累乘的函數
def add():
sum1 = 0
for i in range( 10000):
sum1 += i
print( '累加結果:\n', sum1)
def mul():
mul2 = 1
for i in range( 1, 1000):
mul2 *= i
print( '累乘結果:', '\n', mul2)
start = time. time()
#實例化線程對象
t1 = threading. Thread( target = add)
t2 = threading. Thread( target = mul)
#將每個線程都追加到列表中
l = []
l. append( t1)
l. append( t2)
#初始化每個線程對象
for t in l:
t. start()
#加入join設置
for t in l:
t. join()
print( '程序一共運行{}秒'. format( time. time() - start))
1、同步
一個進程需要接受外部數據,即執行到一個IO操作(等待外部數據)的時候,程序就一直“等著”,不會往下執行;直到接收到了數據
2、異步
一個進程當執行到一個IO操作時,不會等著,會繼續執行後續的操作,某時刻接收到了數據,再返回來處理;很多等待都是沒有意義的,因此異步效率更高
概念分析
(1)Cpython用來組織多線程同時運行的一種手段,是Cpython解釋器默認的一種設定。也就說明無論我們開啟多少個線程,有多少個CPU(或者多核),Python在執行的時候都會在同一時刻只運行一個線程。
(2)之所以加了這個解釋器鎖,是python的發明者為了維持解釋器的穩定運行,但對於用戶而言,卻是一道障礙,會讓我們的進程運行效率不高。
(3)對GIL的精簡解釋:因為有GIL,所以同一時刻,只有一個線程被CPU執行,導致我們的多核CPU能力無法展現。
(4)但是GIL是無法去除的,只能從其他方面來提高效率,比如多進程+協程,因為我們無法實現一個進程裡面跑多個線程,那就直接開多個進程。
任務的分類
(1)IO密集型:需要多次等待外部的數據,所以CPU有非常多的空閒時間,我們就可以利用那些空閒時間來做其他任務,因此有GIL的python適合執行IO密集型的任務,還可以采用多進程+協程來提高效率。
(2)計算密集型:需要連續執行下去不能中斷的任務,如計算累乘、計算π的值。
(3)總結:Python的多線程對於IO密集型任務相當有意義,而對於計算密集型的任務則比較低效率,Python不適用。
(1)串行計算:
import threading
import time
#定義累加和累乘的函數
def sub():
global num
temp = num
time. sleep( 0.0001)
num = temp - 1
start = time. time()
num = 100
l = []
#實例化線程對象
for i in range( 100):
t1 = threading. Thread( target = sub)
t1. start()
l. append( t1)
#加入join設置
for t in l:
t. join()
print( '運行結果:{},程序一共運行{}秒'. format( num, time. time() - start))
(2)串行計算結果:
可以看到從100減去1減了100次計算的結果本應該是0,可這裡結果卻是56,而且是變化的數字;
原因就是函數執行中途睡了0.001秒,這個時間不長不短,接近CPU在同一個進程中的切換速度;
就使一部分計算切換過去了,一部分計算還保留原來的值,即有些減去了1,有些還沒減1線程就被切換走了,導致了結算結果的錯誤。
(3)處理方法——加一個同步鎖:
將CPU鎖死,一瞬間只能有一個線程在執行,在這個線程處理完之前都不會讓CPU在線程中切換
將函數內容改成以下代碼就可以防止串行:
#加一個同步鎖
lock = threading. Lock()
#定義累加和累乘的函數
def sub():
#用同步鎖將線程鎖住
lock. acquire()
global num
temp = num
time. sleep( 0.001)
num = temp - 1
#釋放同步鎖
lock. release()
這樣改一下,就能得出正確結果0了
(1)死鎖案例:
import threading
import time
#繼承式線程定義
class MyThread( threading. Thread):
#先獲得A鎖的函數
def actionA( self):
A. acquire() #獲得鎖
print( self. name, 'gotA', time. ctime()) #輸出線程名和時間
time. sleep( 2)
#在沒有釋放A鎖的情況下再獲取B鎖
B. acquire()
print( self. name, 'gotB', time. ctime())
time. sleep( 1)
#釋放A鎖和B鎖
B. release()
A. release()
#先獲得B鎖的函數
def actionB( self):
B. acquire() #獲得鎖
print( self. name, 'gotB', time. ctime()) #輸出線程名和時間
time. sleep( 2)
#在沒有釋放A鎖的情況下再獲取B鎖
A. acquire()
print( self. name, 'gotA', time. ctime())
time. sleep( 1)
#釋放A鎖和B鎖
A. release()
B. release()
def run( self):
#執行兩個動作
self. actionA()
self. actionB()
if __name__ == '__main__':
#創建兩個鎖
A = threading. Lock()
B = threading. Lock()
L = []
#執行5次實例化對象,但因為死鎖,實際只會執行一次
for i in range( 5):
t = MyThread()
t. start()
L. append( t)
for i in L:
i. join()
(2)死鎖結果:
Thread 1在actionB中將B鎖鎖住了,沒有釋放,然後Thread 2將A鎖鎖住;
之後先釋放A鎖,但此時進程正處於Thread 2上,該進程上沒有A鎖,無法釋放A鎖;
而Thread 1上是B鎖,由於Thread 2沒有執行完,被鎖住的,所以Thread 1上的B鎖也不會被釋放;
就這樣一直僵持著,導致程序只會執行一遍,之後一直處於死鎖狀態。
(1)遞歸鎖測試:
import threading
import time
#繼承式線程定義
class MyThread( threading. Thread):
#先獲得A鎖的函數
def actionA( self):
r_lock. acquire() #獲得鎖
print( self. name, 'gotA', time. ctime()) #輸出線程名和時間
time. sleep( 2)
#在沒有釋放A鎖的情況下再獲取B鎖
r_lock. acquire()
print( self. name, 'gotB', time. ctime())
time. sleep( 1)
#釋放A鎖和B鎖
r_lock. release()
r_lock. release()
#先獲得B鎖的函數
def actionB( self):
r_lock. acquire() #獲得鎖
print( self. name, 'gotB', time. ctime()) #輸出線程名和時間
time. sleep( 2)
#在沒有釋放A鎖的情況下再獲取B鎖
r_lock. acquire()
print( self. name, 'gotA', time. ctime())
time. sleep( 1)
#釋放A鎖和B鎖
r_lock. release()
r_lock. release()
def run( self):
#執行兩個動作
self. actionA()
self. actionB()
if __name__ == '__main__':
start = time. time()
#創建兩個鎖
#A= threading.Lock()
#B= threading.Lock()
#創建一個遞歸鎖
r_lock = threading. RLock()
L = []
#執行5次實例化對象,但因為死鎖,實際只會執行一次
for i in range( 5):
t = MyThread()
t. start()
L. append( t)
for i in L:
i. join()
print( '程序一共執行%s秒' %( time. time() - start))
(2)遞歸鎖結果:
這樣它總共會創建5個線程,5個線程接替遞歸使用,某個進程被占用時就用另外的空閒線程來執行任務,就不會有相互競爭的死鎖狀態;
就相當於遞歸鎖這一把鎖就代表了5把鎖,5把鎖有空閒的都能用。