線程同步
Lock、Rlock鎖機(jī)制
使用鎖的原因
為了避免線程間進(jìn)行數(shù)據(jù)競(jìng)爭(zhēng)蟀架,有時(shí)必須使用一些機(jī)制來(lái)強(qiáng)制線程同步瓣赂。因?yàn)镃python解釋器中GIL(全局解釋鎖)的存在,在每一時(shí)刻只有一個(gè)線程在CPU中執(zhí)行片拍,每個(gè)線程執(zhí)行了一定數(shù)量的字節(jié)碼或者過(guò)了一定的時(shí)間切片再或者遇到了IO操作煌集,CPU就會(huì)切換其他線程執(zhí)行部分字節(jié)碼。那么如果使用兩個(gè)線程分別執(zhí)行add()捌省、reduce()方法苫纤,在理論情況下最后的結(jié)果num可能是0、1纲缓、-1卷拘,因?yàn)榇a分解為字節(jié)碼時(shí),就分解成了很多步驟祝高,有可能某個(gè)線程只執(zhí)行了一些字節(jié)碼栗弟,又去執(zhí)行另一個(gè)線程的字節(jié)碼,這樣就可能造成結(jié)果的錯(cuò)亂工闺。
import dis
num = 0
def add(num):
num += 1
return num
def reduce(num):
num -= 1
return num
print(dis.dis(add))
print(dis.dis(reduce))
下面就是add()乍赫、reduce()方法執(zhí)行的字節(jié)碼,在極端情況下陆蟆,如果add雷厂、reduce每執(zhí)行一個(gè)字節(jié)碼就切換一次,那么結(jié)果就不0叠殷,就是1或者-1
5 0 LOAD_FAST 0 (num) 加載變量num到內(nèi)存
2 LOAD_CONST 1 (1) 加載變量1到內(nèi)存
4 INPLACE_ADD 執(zhí)行加法
6 STORE_FAST 0 (num) 將結(jié)果賦值給num
6 8 LOAD_FAST 0 (num)
10 RETURN_VALUE None
10 0 LOAD_FAST 0 (num)
2 LOAD_CONST 1 (1)
4 INPLACE_SUBTRACT
6 STORE_FAST 0 (num)
11 8 LOAD_FAST 0 (num)
10 RETURN_VALUE None
使用鎖的方式
為了避免上面出現(xiàn)的情況改鲫,必須給某些代碼段加上鎖
import threading
num = 0
def add(lock):
global num
for i in range(1000000):
lock.acquire()
num += 1
lock.release()
def reduce(lock):
global num
for i in range(1000000):
lock.acquire()
num -= 1
lock.release()
# 創(chuàng)建鎖
lock = threading.Lock()
# 創(chuàng)建線程
t1 = threading.Thread(target=add, args=(lock, ))
t2 = threading.Thread(target=reduce, args=(lock, ))
t1.start()
t2.start()
# 等待子進(jìn)程執(zhí)行完
t1.join()
t2.join()
print(num)
這樣結(jié)果就是0,但是加鎖雖然達(dá)到了我們期望的結(jié)果林束,但是執(zhí)行程序花費(fèi)的時(shí)間卻長(zhǎng)了钩杰。
可重入的鎖
在一個(gè)線程中,我們?cè)诓糠执a中使用了鎖lock.acquire()還沒(méi)有釋放鎖時(shí)诊县,調(diào)用了另一個(gè)方法讲弄,方法中又使用了鎖,這樣如果使用同一把普通的鎖很容易出現(xiàn)死鎖依痊。這時(shí)我們可以使用Rlock避除,但是要注意使用了幾次Rlock.acquire()怎披,就必須使用幾次Rlock.release(),下面是示例代碼
import threading
num = 0
def add(rlock):
global num
for i in range(1000000):
rlock.acquire()
num += 1
dosomething(rlock)
rlock.release()
def reduce(rlock):
global num
for i in range(1000000):
rlock.acquire()
num -= 1
dosomething(rlock)
rlock.release()
def dosomething(rlock):
# 模擬其他操作的函數(shù)
rlock.acquire()
# dosomething
rlock.release()
# 創(chuàng)建鎖
rlock = threading.RLock()
# 創(chuàng)建線程
t1 = threading.Thread(target=add, args=(rlock,))
t2 = threading.Thread(target=reduce, args=(rlock,))
t1.start()
t2.start()
# 等待子進(jìn)程執(zhí)行完
t1.join()
t2.join()
print(num)
使用鎖的壞處
因?yàn)閯?chuàng)建鎖解鎖都要花費(fèi)時(shí)間瓶摆,會(huì)影響程序性能凉逛;并且在使用鎖時(shí),容易產(chǎn)生死鎖群井。
產(chǎn)生死鎖的方式:1.創(chuàng)建鎖状飞,沒(méi)有解鎖 ; 2.兩個(gè)線程互相有對(duì)方的鎖,然后互相等待书斜。
Condition機(jī)制
條件同步诬辈,用戶復(fù)雜的線程間同步。內(nèi)部使用的也是lock或者Rlock
重要的方法
-
wait(timeout=None)
當(dāng)前線程等待著另外的線程發(fā)起通知荐吉,才向下執(zhí)行焙糟。
-
notify()
發(fā)出通知
示例代碼
需要了解的知識(shí)都在代碼中的注釋
import threading
"""
張三:床前明月光
李四:疑是地上霜
張三:舉頭望明月
李四:低頭思故鄉(xiāng)
怎么實(shí)現(xiàn)兩個(gè)線程的交替說(shuō)話,如果只有兩句样屠,可以使用鎖機(jī)制穿撮,讓某個(gè)線程先執(zhí)行,這里有多句話交替出現(xiàn)痪欲,最好使用condition
"""
class ZSThead(threading.Thread):
def __init__(self, name, cond):
super(ZSThead, self).__init__()
self.name = name
self.cond = cond
def run(self):
# 必須先調(diào)用with self.cond悦穿,才能使用wait()、notify()方法
with self.cond:
# 講話
print("{}:床前明月光".format(self.name))
# 等待李四的回應(yīng)
self.cond.notify()
self.cond.wait()
# 講話
print("{}:舉頭望明月".format(self.name))
# 等待李四的回應(yīng)
self.cond.notify()
self.cond.wait()
class LSThread(threading.Thread):
def __init__(self, name, cond):
super(LSThread, self).__init__()
self.name = name
self.cond = cond
def run(self):
with self.cond:
# wait()方法不僅能獲得一把鎖业踢,并且能夠釋放cond的大鎖咧党,這樣張三才能進(jìn)入with self.cond中
self.cond.wait()
print(f"{self.name}:疑是地上霜")
# notify()釋放wait()生成的鎖
self.cond.notify()
self.cond.wait()
print(f"{self.name}:低頭思故鄉(xiāng)")
self.cond.notify()
cond = threading.Condition()
zs = ZSThead("張三", cond)
ls = LSThread("李四", cond)
# 啟動(dòng)順序很重要,必須先啟動(dòng)李四陨亡,讓他在那里等待著傍衡,
# 因?yàn)橄葐?dòng)張三時(shí),他說(shuō)了話就發(fā)出了通知负蠕,但是當(dāng)時(shí)李四的進(jìn)程還沒(méi)有啟動(dòng)蛙埂,
# 并且condition外面的大鎖也沒(méi)有釋放,李四也沒(méi)法獲取self.cond這把大鎖
# condition有兩層鎖遮糖,一把底層鎖在線程調(diào)用了wait()方法就會(huì)釋放
# 每次調(diào)用wait()方法后绣的,都會(huì)創(chuàng)建一把鎖放進(jìn)condition的雙向隊(duì)列中,等待notify()方法的喚醒
ls.start()
zs.start()