一、互斥鎖(Mutex)
在上節最后我們講到了線程安全,線程同步能夠保證多個線程安全訪問競爭資源,最簡單的同步機制是引入互斥鎖。互斥鎖為資源引入一個狀態:鎖定/非鎖定。某個線程要更改共享數據時,先將其鎖定,此時資源的狀態為“鎖定”,其他線程不能更改;直到該線程釋放資源,將資源的狀態變成“非鎖定”,其他的線程才能再次鎖定該資源。互斥鎖保證了每次只有一個線程進行寫入操作,從而保證了多線程情況下數據的正確性。
threading模塊中定義了Lock類,可以方便的處理鎖定:
#創建鎖 lock = threading.Lock() #鎖定 lock.acquire(blocking=True, timeout=-1) #釋放 lock.release()
還是以上節實例為例:
# -*- coding: UTF-8 -*-
import threading
class MyThread(threading.Thread):
def run(self):
global n
lock.acquire()
n += 1
lock.release()
print(self.name + ' set n to ' + str(n))
n = 0
lock = threading.Lock()
if __name__ == '__main__':
for i in range(5):
t = MyThread()
t.start()
print('final num: %d' % n)
輸出:
Thread-1 set n to 1 Thread-2 set n to 2 Thread-3 set n to 3 Thread-4 set n to 4 Thread-5 set n to 5 final num: 5
加鎖之后,我們可以確保數據的正確性
二、死鎖
死鎖是指一個資源被多次調用,而多次調用方都未能釋放該資源就會造成一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖。
2.1 一個線程內部多次加鎖卻沒有釋放
import threading
class MyThread(threading.Thread):
def run(self):
global n1, n2
lock.acquire() # 加鎖
n1 += 1
print(self.name + ' set n1 to ' + str(n1))
lock.acquire() # 再次加鎖
n2 += n1
print(self.name + ' set n2 to ' + str(n2))
lock.release()
lock.release()
n1, n2 = 0, 0
lock = threading.Lock()
if __name__ == '__main__':
thread_list = []
for i in range(5):
t = MyThread()
t.start()
thread_list.append(t)
for t in thread_list:
t.join()
print('final num:%d ,%d' % (n1, n2))
結果:
Thread-1 set n1 to 1 # 會一直等待
2.2 多個程序間相互調用引起死鎖
import threading,time
def run1():
print("grab the first part data")
lock.acquire()
global num
num +=1
lock.release()
return num
def run2():
print("grab the second part data")
lock.acquire()
global num2
num2+=1
lock.release()
return num2
def run3():
lock.acquire()
res = run1()
print('--------between run1 and run2-----')
res2 = run2()
lock.release()
print(res,res2)
if __name__ == '__main__':
num,num2 = 0,0
lock = threading.Lock()
for i in range(10):
t = threading.Thread(target=run3)
t.start()
while threading.active_count() != 1:
print(threading.active_count())
else:
print('----all threads done---')
print(num,num2)
三、遞歸鎖
上述兩個問題都可以使用python的遞歸鎖解決
# 遞歸鎖 rlock = threading.RLOCK()
RLock內部維護着一個Lock和一個counter變量,counter記錄了acquire的次數,從而使得資源可以被多次require。直到一個線程所有的acquire都被release,其他的線程才能獲得資源。這里以例1為例,如果使用RLock代替Lock,則不會發生死鎖:
# -*- coding: UTF-8 -*-
import threading
class MyThread(threading.Thread):
def run(self):
global n1, n2
lock.acquire() # 加鎖
n1 += 1
print(self.name + ' set n1 to ' + str(n1))
lock.acquire() # 再次加鎖
n2 += n1
print(self.name + ' set n2 to ' + str(n2))
lock.release()
lock.release()
n1, n2 = 0, 0
lock = threading.RLock()
if __name__ == '__main__':
thread_list = []
for i in range(5):
t = MyThread()
t.start()
thread_list.append(t)
for t in thread_list:
t.join()
print('final num:%d ,%d' % (n1, n2))
輸出:
Thread-1 set n1 to 1 Thread-1 set n2 to 1 Thread-2 set n1 to 2 Thread-2 set n2 to 3 Thread-3 set n1 to 3 Thread-3 set n2 to 6 Thread-4 set n1 to 4 Thread-4 set n2 to 10 Thread-5 set n1 to 5 Thread-5 set n2 to 15 final num:5 ,15
