如果多個(gè)線程共同對某個(gè)數(shù)據(jù)修改,則可能出現(xiàn)不可預(yù)料的結(jié)果睁本,為了保證數(shù)據(jù)的正確性,需要對多個(gè)線程進(jìn)行同步,使用 Thread 對象的 Lock 和 Rlock 可以實(shí)現(xiàn)簡單的線程同步忠怖,這兩個(gè)對象都有 acquire 方法和 release 方法,分別用來獲取和釋放鎖
Lock和RLock有什么區(qū)別呢呢堰?很多教程都只提到RLock可以多次acquire和release,舉的例子就是類似這種:
import threading
rlock=threading.RLock()
def foo():
rlock.acquire()
rlock.acquire() # 可以多次獲得鎖
print('multi-acquire')
rlock.release()
rlock.acquire() # acquire幾次鎖就要release 幾次
t=threading.Thread(target=foo)
t.start()
t.join()
#輸出
multi-acquire
看完了還是一頭霧水脑又,雖然知道了RLock可以多次獲取釋放暮胧,但有什么用呢?獲取一次鎖就行了為啥還要多次獲取呢问麸? 下面就舉例說一下這個(gè)RLock可以多次獲取鎖的好處
假如在多線程環(huán)境下我們要對一個(gè)變量num進(jìn)行加一操作往衷,并且加一操作前我們需要檢查這個(gè)變量是不是負(fù)值
import threading
lock=threading.Lock()
num=1
def check(): #檢查變量是否小于零
global num
lock.acquire()
if num<0:
print('num<0')
else:
print('num>1')
lock.release()
def add(): # 對變量進(jìn)行+1操作
global num
lock.acquire()
check() #加一操作前檢查num的值是否小于零
num += 1
lock.release()
t=threading.Thread(target=add)
t.start()
t.join()
上面的這段代碼會發(fā)生死鎖情況,因?yàn)樵赼dd函數(shù)里先通過lock.acquire()獲取了鎖严卖,然后調(diào)用check函數(shù)的時(shí)候席舍,check函數(shù)里又在獲取鎖,這就造成死鎖情況哮笆。這種情況也就是在一個(gè)加鎖的操作里調(diào)用另一個(gè)加鎖的方法来颤,而且加的是同一把鎖,就會發(fā)生死鎖稠肘。解決方法很簡單福铅,就是把Lock換成RLock就行了
讓我們在舉一個(gè)實(shí)用點(diǎn)的例子
現(xiàn)在要使用遞歸方法計(jì)算一個(gè)數(shù)的累乘,并要求加鎖實(shí)現(xiàn)計(jì)算過程中不會被其他線程干擾
from threading import Lock
lock = Lock()
def factorial(n):
assert n > 0
if n == 1:
return 1
with lock:
out = n * factorial(n - 1)
return out
print(factorial(3)) #發(fā)生死鎖项阴,無法執(zhí)行
上面遞歸的函數(shù)里使用with 語句簡化了鎖的acquire和release 寫法滑黔。
在with語句里又遞歸調(diào)用了factorial函數(shù)本身,這就會發(fā)生再次請求鎖的情況环揽,所以第一次遞歸的時(shí)候就發(fā)生了死鎖略荡,解決的方法還是一樣,把Lock換成RLock即可
上面的例子說明了多次獲取鎖的實(shí)際用途歉胶,其實(shí)RLock和Lock的區(qū)別還有另一點(diǎn)汛兜,就是:
Lock獲取的鎖可以被其他任何線程直接釋放
RLock獲取的鎖只有獲取這個(gè)鎖的線程自己才能釋放
看下面的例子:
import threading
import time
lock = threading.Lock()
def a():
lock.acquire()
time.sleep(3)
lock.release()
def b():
lock.release()
print('lock released')
t1=threading.Thread(target=a)
t2=threading.Thread(target=b)
t1.start()
t2.start()
t1.join()
t2.join()
#輸出:
lock released
Exception in thread Thread-1:
Traceback (most recent call last):
File "D:\python3.6\lib\threading.py", line 916, in _bootstrap_inner
self.run()
File "D:\python3.6\lib\threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "C:/Users/test/Desktop/test.py", line 9, in a
lock.release()
RuntimeError: release unlocked lock
t1線程在函數(shù)a里面獲取了鎖,然后sleep 3秒鐘通今,這時(shí)候t2線程的b函數(shù)在沒有獲取鎖的情況下直接就釋放的鎖粥谬,然后執(zhí)行print('lock released')打印輸出,等3秒后線程t1醒來后再試圖釋放一個(gè)已經(jīng)被釋放的鎖的時(shí)候辫塌,就會報(bào)RuntimeError: release unlocked lock 錯(cuò)了帝嗡,說明t2線程可以獲取t1線程的lock鎖
如果把上面的鎖改成RLock,輸出會變成這樣
Exception in thread Thread-2:
Traceback (most recent call last):
File "D:\python3.6\lib\threading.py", line 916, in _bootstrap_inner
self.run()
File "D:\python3.6\lib\threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "C:/Users/test/Desktop/test.py", line 13, in b
lock.release()
RuntimeError: cannot release un-acquired lock
報(bào)錯(cuò)信息:無法釋放一個(gè)沒有獲取到的鎖璃氢, 說明t2 線程的b函數(shù)里,無法獲取t1線程獲取的rlock鎖