Python的GIL:全局解釋鎖(Global Interpreter Lock)在執行多線程時,同一時刻至多隻有一個線程被CPU執行,如果要實現並行可以使用多進程。
Python適用於IO密集型情況,不太適用於計算密集型的場景,因爲執行計算時線程間的切換會耗費時間。
下面有三種情況
import threading
import time
def sub():
global num
temp=num
print('A',end='|')
num=temp-1
print('B',end='|')
num=30
l=[]
for i in range(30):
t=threading.Thread(target=sub)
l.append(t)
t.start()
for t in l:
t.join()
print('\n',num)
#結果爲0
import threading
import time
def sub():
global num
temp=num
print('A',end='|')
time.sleep(1)
num=temp-1
print('B',end='|')
num=30
l=[]
for i in range(30):
t=threading.Thread(target=sub)
l.append(t)
t.start()
for t in l:
t.join()
print('\n',num)
#結果爲29
import threading
import time
def sub():
global num
temp=num
print('A',end='|')
time.sleep(0.001)
num=temp-1
print('B',end='|')
num=30
l=[]
for i in range(30):
t=threading.Thread(target=sub)
l.append(t)
t.start()
for t in l:
t.join()
print('\n',num)
#每次的結果都不同
第一種情況,因爲程序運行時間太短,所以線程間不用切換,相當於線程一個一個執行每次都減1故結果爲0,從輸出的AB情況也可以看出
第二種情況,當碰到sleep時解釋器會進行線程的任務切換,而且sleep1秒的時間過長,導致每個線程的temp都拿到num的初始值30,然後全部進行減1操作將結果29又賦給num,所以最好num等於29,從輸出的AB情況也可以看出
第三種情況,碰到sleep時開始切換線程,但是由於sleep時間短,只有部分線程temp拿到num的初始值30,然後程序又切回原來的線程進行減1運算,將結果賦給num,而後面的部分線程的temp獲得新的num值,而後又切換回來做減1運算,然後後面的線程獲得新的num值,如此往復直到結束。因爲每次切換的時間點並不唯一,所以最後的num值也不一樣。從AB的情況也可以看出。
要得到0的結果還可以用Lock鎖
import threading
import time
def sub():
global num
lock.acquire()#獲取鎖
temp=num
print('A',end='|')
time.sleep(0.01)
num=temp-1
lock.release()#釋放鎖
print('B',end='|')
lock=threading.Lock()
num=30
l=[]
for i in range(30):
t=threading.Thread(target=sub)
l.append(t)
t.start()
for t in l:
t.join()
print('\n',num)
在線程獲取鎖之後,只能執行鎖下面的程序,直到鎖被釋放,才能執行其他線程
死鎖的情況
import threading
import time
class MyThread(threading.Thread):
def actionA(self):
A.acquire()
print(self.name,'gotA',time.ctime())
time.sleep(2)
B.acquire()
print(self.name,'gotB',time.ctime())
time.sleep(1)
B.release()
A.release()
def actionB(self):
B.acquire()
print(self.name,'gotB',time.ctime())
time.sleep(2)
A.acquire()
print(self.name,'gotA',time.ctime())
time.sleep(1)
A.release()
B.release()
def run(self):
self.actionA()
self.actionB()
if __name__ == '__main__':
A=threading.Lock()
B=threading.Lock()
l=[]
for i in range(5):
t=MyThread()
l.append(t)
t.start()
for t in l:
t.join()
print('ending...')
結果爲Thread-1 gotA Thu Apr 19 20:14:41 2018#線程1開始執行actionA,獲得A鎖
Thread-1 gotB Thu Apr 19 20:14:43 2018#線程1,獲得B鎖,並於1秒後釋放B鎖和A鎖
Thread-1 gotB Thu Apr 19 20:14:44 2018#線程1執行actionB,獲得B鎖,碰到sleep,進行線程切換
Thread-2 gotA Thu Apr 19 20:14:44 2018#線程2開始執行actionA,獲得A鎖
#線程1要繼續運行就要獲得A鎖,線程2要繼續運行就要獲得B鎖,但是鎖都到對方手中,所以造成程序無法繼續執行,就是死鎖
要解決以上問題可以用遞歸鎖
import threading
import time
class MyThread(threading.Thread):#用類來實現多線程,必須繼承threading.Thread,且必須定義run函數,因爲t.start()就是調用run函數
def actionA(self):
r_lock.acquire()#count=1
print(self.name,'gotA',time.ctime())
time.sleep(2)
r_lock.acquire()#count=2
print(self.name,'gotB',time.ctime())
time.sleep(1)
r_lock.release()#count=1
r_lock.release()#count=0
def actionB(self):
r_lock.acquire()
print(self.name,'gotB',time.ctime())
time.sleep(2)
r_lock.acquire()
print(self.name,'gotA',time.ctime())
time.sleep(1)
r_lock.release()
r_lock.release()
def run(self):
self.actionA()
self.actionB()
if __name__ == '__main__':
r_lock=threading.RLock()
l=[]
for i in range(5):
t=MyThread()
l.append(t)
t.start()
for t in l:
t.join()
print('ending...')
遞歸鎖裏有個計數器,當遞歸鎖被acquire一次計數器就加一,release一次計數器就減1。
只有當計數器等於0時才能被其他的線程獲得,大於0時只能執行現在獲得鎖的線程,直到計數器再次變爲0時,其他線程纔有可能獲得遞歸鎖,解決了死鎖的情況