目錄
-
第十五章、Python多線程同步鎖,死鎖和遞歸鎖
- 1. 引子:
- 2.同步鎖
-
3.死鎖
- 引子:
-
4.遞歸鎖RLock
- 原理:
- 不多說,放代碼
- 總結(jié):
- 5. 大總結(jié)
第十五章、Python多線程同步鎖,死鎖和遞歸鎖
1. 引子:
1.創(chuàng)建線程對(duì)象
t1 = threading.Thread(target=say,args=('tony',))
2.啟動(dòng)線程
t1.start()
后面又說了兩個(gè)點(diǎn)就是join和守護(hù)線程的概念
? 以上就是python多線程的基本使用
? 說明:前面說的兩個(gè)功能是相互獨(dú)立的,相互不干涉的,不會(huì)用到同享的資源或者數(shù)據(jù),如果我們多個(gè)線程要用到相同的數(shù)據(jù),那么就會(huì)存在資源爭(zhēng)用和鎖的問題,不管在什么語(yǔ)言中,這個(gè)都是不能避免的。 那么接下來講講同步鎖,死鎖和遞歸鎖的使用
2.同步鎖
? 鎖通常被用來實(shí)現(xiàn)對(duì)共享資源的同步訪問。為每一個(gè)共享資源創(chuàng)建一個(gè)Lock對(duì)象,當(dāng)你需要訪問該資源時(shí),調(diào)用acquire方法來獲取鎖對(duì)象(如果其它線程已經(jīng)獲得了該鎖,則當(dāng)前線程需等待其被釋放),待資源訪問完后,再調(diào)用release方法釋放鎖。
? 適用同步鎖的例子如下:
import threading
import time
num = 100
def fun_sub():
global num
# num -= 1
num2 = num
time.sleep(0.001)
num = num2-1
if __name__ == '__main__':
print('開始測(cè)試同步鎖 at %s' % time.ctime())
thread_list = []
for thread in range(100):
t = threading.Thread(target=fun_sub)
t.start()
thread_list.append(t)
for t in thread_list:
t.join()
print('num is %d' % num)
print('結(jié)束測(cè)試同步鎖 at %s' % time.ctime())
-----------------------------------------------------
開始測(cè)試同步鎖 at Sun Apr 28 09:56:45 2019
num is 91
結(jié)束測(cè)試同步鎖 at Sun Apr 28 09:56:45 2019
這樣的例子描述 :創(chuàng)建100的線程,然后每個(gè)線程去從公共資源num變量去執(zhí)行減1操作,按照正常情況下面,等到代碼執(zhí)行結(jié)束,打印num變量,應(yīng)該得到的是0,因?yàn)?00個(gè)線程都去執(zhí)行了一次減1的操作。
這樣的問題是 :發(fā)現(xiàn)結(jié)果不是0而是91
讓我們整理一下代碼思路 :
1.因?yàn)镚IL,只有一個(gè)線程(假設(shè)線程1)拿到了num這個(gè)資源,然后把變量賦值給num2,sleep 0.001秒,這時(shí)候num=100
2.當(dāng)?shù)谝粋€(gè)線程sleep 0.001秒這個(gè)期間,這個(gè)線程會(huì)做yield操作,就是把cpu切換給別的線程執(zhí)行(假設(shè)線程2拿到個(gè)GIL,獲得cpu使用權(quán)),線程2也和線程1一樣也拿到num,返回賦值給num2,然后sleep,這時(shí)候,其實(shí)num還是=100.
3.線程2 sleep時(shí)候,又要yield操作,假設(shè)線程3拿到num,執(zhí)行上面的操作,其實(shí)num有可能還是100
4.等到后面cpu重新切換給線程1,線程2,線程3上執(zhí)行的時(shí)候,他們執(zhí)行減1操作后,其實(shí)等到的num其實(shí)都是99,而不是順序遞減的。
5.其他剩余的線程操作如上
解決方案 :這里就要借助于python的同步鎖了,也就是同一時(shí)間只能放一個(gè)線程來操作num變量,減1之后,后面的線程操作來操作num變量。看看下面我們?cè)趺磳?shí)現(xiàn)。
import threading
import time
num = 100
def fun_sub():
global num
lock.acquire()
print('----加鎖----')
print('現(xiàn)在操作共享資源的線程名字是:',t.name)
num2 = num
time.sleep(0.001)
num = num2-1
lock.release()
print('----釋放鎖----')
if __name__ == '__main__':
print('開始測(cè)試同步鎖 at %s' % time.ctime())
lock = threading.Lock() #創(chuàng)建一把同步鎖
thread_list = []
for thread in range(100):
t = threading.Thread(target=fun_sub)
t.start()
thread_list.append(t)
for t in thread_list:
t.join()
print('num is %d' % num)
print('結(jié)束測(cè)試同步鎖 at %s' % time.ctime())
------------------------------------------------
.......
----加鎖----
現(xiàn)在操作共享資源的線程名字是: Thread-98
----釋放鎖----
----加鎖----
現(xiàn)在操作共享資源的線程名字是: Thread-100
----釋放鎖----
num is 0
結(jié)束測(cè)試同步鎖 at Sun Apr 28 12:08:27 2019
思路 :看到上面我們給中間的減1代碼塊,加個(gè)一把同步鎖,這樣,我們就可以得到我們想要的結(jié)果了,這就是同步鎖的作用,一次只有一個(gè)線程操作同享資源。
3.死鎖
引子:
? 死鎖的這個(gè)概念在很多地方都存在,比較在數(shù)據(jù)中,大概介紹下死鎖是怎么產(chǎn)生的
# 線程1拿到了(鎖頭2)想要往下執(zhí)行需要(鎖頭1),
# 線程2拿到了(鎖頭1)想要往下執(zhí)行需要(鎖頭2)
# 互相都拿到了彼此想要往下執(zhí)行的必需條件,互相都不放手里的鎖頭.
# 產(chǎn)生了死鎖問題
產(chǎn)生原因: python中在線程間共享多個(gè)資源的時(shí)候,如果兩個(gè)線程分別占有一部分資源并且同時(shí)等待對(duì)方的資源,就會(huì)造成死鎖,因?yàn)橄到y(tǒng)判斷這部分資源都正在使用,所有這兩個(gè)線程在無外力作用下將一直等待下去。
from threading import Thread,Lock
mutex1 = Lock()
mutex2 = Lock()
import time
class MyThreada(Thread):
def run(self):
self.task1()
self.task2()
def task1(self):
mutex1.acquire()
print(f'{self.name} 搶到了 鎖1 ')
mutex2.acquire()
print(f'{self.name} 搶到了 鎖2 ')
mutex2.release()
print(f'{self.name} 釋放了 鎖2 ')
mutex1.release()
print(f'{self.name} 釋放了 鎖1 ')
def task2(self):
mutex2.acquire()
print(f'{self.name} 搶到了 鎖2 ')
time.sleep(1)
mutex1.acquire()
print(f'{self.name} 搶到了 鎖1 ')
mutex1.release()
print(f'{self.name} 釋放了 鎖1 ')
mutex2.release()
print(f'{self.name} 釋放了 鎖2 ')
for i in range(3):
t = MyThreada()
t.start()
那么,為了解決這個(gè)死鎖問題,就引入了遞歸鎖方案
4.遞歸鎖RLock
原理:
? 為了支持在同一線程中多次請(qǐng)求同一資源,python提供了"遞歸鎖":threading.RLock。RLock內(nèi)部維護(hù)著一個(gè)Lock和一個(gè)counter變量,counter記錄了acquire的次數(shù),從而使得資源可以被多次acquire。直到一個(gè)線程所有的acquire都被release,其他的線程才能獲得資源
不多說,放代碼
from threading import Thread,Lock,RLock
# mutex1 = Lock()
# mutex2 = Lock()
mutex1 = RLock()
mutex2 = mutex1
import time
class MyThreada(Thread):
def run(self):
self.task1()
self.task2()
def task1(self):
mutex1.acquire()
print(f'{self.name} 搶到了 鎖1 ')
mutex2.acquire()
print(f'{self.name} 搶到了 鎖2 ')
mutex2.release()
print(f'{self.name} 釋放了 鎖2 ')
mutex1.release()
print(f'{self.name} 釋放了 鎖1 ')
def task2(self):
mutex2.acquire()
print(f'{self.name} 搶到了 鎖2 ')
time.sleep(1)
mutex1.acquire()
print(f'{self.name} 搶到了 鎖1 ')
mutex1.release()
print(f'{self.name} 釋放了 鎖1 ')
mutex2.release()
print(f'{self.name} 釋放了 鎖2 ')
for i in range(3):
t = MyThreada()
t.start()
總結(jié):
? 上面我們用一把遞歸鎖,就解決了多個(gè)同步鎖導(dǎo)致的死鎖問題。大家可以把RLock理解為大鎖中還有小鎖,只有等到內(nèi)部所有的小鎖,都沒有了,其他的線程才能進(jìn)入這個(gè)公共資源。
5. 大總結(jié)
? 還有一點(diǎn),并不是所有的多線程都存在數(shù)據(jù)不同步、死鎖的問題,但在訪問共享資源的時(shí)候,鎖是一定要存在了, 所以我們?cè)诖a里面加鎖的時(shí)候,要注意在什么地方加,對(duì)性能的影響最小,這個(gè)就靠對(duì)邏輯的理解了。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
