threading 模塊的類 Lock 的基本使用
1. 簡(jiǎn)介
在多線程應(yīng)用中,某個(gè)資源被多個(gè)線程共享訪問(wèn),線程通過(guò)使用鎖獨(dú)占該資源。需要獨(dú)占訪問(wèn)的資源可能是:
- 打印機(jī),線程在使用打印機(jī)時(shí),不允許其它線程向打印機(jī)輸出
- 共享變量,線程對(duì)這個(gè)變量進(jìn)行讀取訪問(wèn)時(shí),不允許其它線程同時(shí)對(duì)這個(gè)變量進(jìn)行讀取訪問(wèn)
python 的 threading 模塊提供了類 Lock 用于獨(dú)占訪問(wèn)某個(gè)共享資源,類 Lock 提供了如下方法:
方法 | 功能 |
---|---|
acquire() | 獲得鎖,如果鎖是空閑的,則立即返回;如果鎖已經(jīng)被其它線程占用了,則阻塞等待。 |
release() | 釋放鎖,喚醒等待該鎖的線程。 |
線程在獨(dú)占使用某個(gè)資源前,需要調(diào)用 lock.acquire() 方法,使用完畢后,需要調(diào)用 lock.release() 方法,如下所示:
lock = threading.Lock()
lock.acquire()
獨(dú)占訪問(wèn)某個(gè)資源
lock.release()
2. 數(shù)據(jù)競(jìng)爭(zhēng)
當(dāng)多個(gè)線程在讀寫(xiě)某個(gè)共享變量時(shí),其最終的結(jié)果依賴于線程的執(zhí)行順序,這種現(xiàn)象被稱為數(shù)據(jù)競(jìng)爭(zhēng),示例如下:
import threading
sum = 0
tmp = 0
- 引入模塊 threading
- 設(shè)定全局變量 sum 和 tmp 的初值為 0,它們被線程共享訪問(wèn)
def thread_entry():
global sum, tmp
for i in range(1000 * 1000):
tmp = sum + 1
sum = tmp
- 在第 1 行,定義線程入口 thread_entry
- 在第 2 行,聲明共享變量 sum 和 tmp
- 在第 4 行,for 循環(huán) 1000* 1000 次,遞增變量 sum
t0 = threading.Thread(target = thread_entry, args = ())
t1 = threading.Thread(target = thread_entry, args = ())
t0.start()
t1.start()
t0.join()
t1.join()
print('sum =', sum)
- 創(chuàng)建線程 t0,線程入口為 thread_entry
- 線程 t0 對(duì)變量 sum 遞增 1000 * 1000 次
- 創(chuàng)建線程 t1,線程入口為 thread_entry
- 線程 t1 對(duì)變量 sum 遞增 1000 * 1000 次
- 等待兩個(gè)線程結(jié)束后,打印 sum 的值
第一次運(yùn)行程序,輸出結(jié)果如下:
sum = 1464661
再次運(yùn)行程序,輸出結(jié)果如下:
sum = 1415592
線程 t0 和 t1 對(duì) sum 各自遞增 1000 * 1000 次,期望最終的 sum 為 2 * 1000 * 1000。然而,線程 t0 和 線程 t1 共享訪問(wèn)變量 sum 和 tmp,存在數(shù)據(jù)競(jìng)爭(zhēng),導(dǎo)致:
- 實(shí)際結(jié)果依賴于線程的執(zhí)行順序,每次執(zhí)行程序的輸出結(jié)果都不一樣
- 實(shí)際結(jié)果和預(yù)期不一致
3. 使用 lock 防止數(shù)據(jù)競(jìng)爭(zhēng)
可以使用 threading 模塊的類 Lock 防止數(shù)據(jù)競(jìng)爭(zhēng),示例如下:
import threading
sum = 0
tmp = 0
def thread_entry():
global sum, tmp
for i in range(1000 * 1000):
lock.acquire() # 獲取鎖
tmp = sum + 1
sum = tmp
lock.release() # 釋放鎖
lock = threading.Lock() # 初始化鎖
t0 = threading.Thread(target = thread_entry, args = ())
t1 = threading.Thread(target = thread_entry, args = ())
t0.start()
t1.start()
t0.join()
t1.join()
print('sum =', sum)
和上個(gè)小節(jié)的例子相比,增加了 3 行代碼 (使用注釋標(biāo)記):
- lock.acquire(),訪問(wèn)共享變量 sum 和 tmp 前,需要獲取鎖
- lock.release(),訪問(wèn)共享變量 sum 和 tmp 后,需要釋放鎖
- lock = thread.Lock(),初始化鎖
第一次運(yùn)行程序,輸出結(jié)果如下:
sum = 200000
再次運(yùn)行程序,輸出結(jié)果如下:
sum = 200000
線程 t0 和 t1 對(duì) sum 各自遞增 1000 * 1000 次,期望最終的 sum 為 2 * 1000 * 1000。使用了 lock 防止了數(shù)據(jù)競(jìng)爭(zhēng):
- 每次執(zhí)行程序的輸出結(jié)果都是相同的
- 實(shí)際結(jié)果和期望結(jié)果相符合