首頁 > 軟體

Python並行程式設計多執行緒鎖機制Lock與RLock實現執行緒同步

2022-07-01 18:00:55

什麼是鎖機制?

要回答這個問題,我們需要知道為什麼需要使用鎖機制。前面我們談到一個程序內的多個執行緒的某些資源是共用的,這也是執行緒的一大優勢,但是也隨之帶來一個問題,即當兩個及兩個以上的執行緒同時存取共用資源時,如果此時沒有預設對應的同步機制,就可能帶來同一時刻多個執行緒同時存取同一個共用資源,即出現競態,多數情況下我們是不希望出現這樣的情況的,那麼怎麼避免呢?

Lock() 管理執行緒

先看一段程式碼:

import threading
import time
resource = 0
count = 1000000
resource_lock = threading.Lock()
def increment():
    global resource
    for i in range(count):
        resource += 1
def decerment():
    global resource
    for i in range(count):
        resource -= 1
increment_thread = threading.Thread(target=increment)
decerment_thread = threading.Thread(target=decerment)
increment_thread.start()
decerment_thread.start()
increment_thread.join()
decerment_thread.join()
print(resource)

執行截圖如下:

執行結果

當我們多次執行時,可以看到最終的結果都幾乎不等於我們期待的值即resource初始值0

為什麼呢? 原因就是因為 += 和 -=並不是原子操作。

可以使用dis模組檢視位元組碼:

import dis
def add(total):
    total += 1
def desc(total):
    total -= 1
total = 0
print(dis.dis(add))
print(dis.dis(desc))
# 執行結果:
#   3           0 LOAD_FAST                0 (total)
#               3 LOAD_CONST               1 (1)
#               6 INPLACE_ADD
#               7 STORE_FAST               0 (total)
#              10 LOAD_CONST               0 (None)
#              13 RETURN_VALUE
# None
#   5           0 LOAD_FAST                0 (total)
#               3 LOAD_CONST               1 (1)
#               6 INPLACE_SUBTRACT
#               7 STORE_FAST               0 (total)
#              10 LOAD_CONST               0 (None)
#              13 RETURN_VALUE
# None

那麼如何保證初始值為0呢? 我們可以利用Lock(),程式碼如下:

import threading
import time
resource = 0
count = 1000000
resource_lock = threading.Lock()
def increment():
    global resource
    for i in range(count):
        resource_lock.acquire()
        resource += 1
        resource_lock.release()
def decerment():
    global resource
    for i in range(count):
        resource_lock.acquire()
        resource -= 1
        resource_lock.release()
increment_thread = threading.Thread(target=increment)
decerment_thread = threading.Thread(target=decerment)
increment_thread.start()
decerment_thread.start()
increment_thread.join()
decerment_thread.join()
print(resource)

執行截圖如下:

執行結果

從執行結果可以看到,不論我們執行多少次改程式碼,其resource的值都為初始值0, 這就是Lock()的功勞,即它可以將某一時刻的存取限定在單個執行緒或者單個型別的執行緒上,在存取鎖定的共用資源時,必須要現獲取對應的鎖才能存取,即要等待其他執行緒釋放資源,即resource_lock.release()當然為了防止我們對某個資源鎖定後,忘記釋放鎖,導致死鎖,我們可以利用上下文管理器管理鎖實現同樣的效果:

import threading
import time
resource = 0
count = 1000000
resource_lock = threading.Lock()
def increment():
    global resource
    for i in range(count):
        with resource_lock:
                resource += 1
def decerment():
    global resource
    for i in range(count):
        with resource_lock:
                resource -= 1
increment_thread = threading.Thread(target=increment)
decerment_thread = threading.Thread(target=decerment)
increment_thread.start()
decerment_thread.start()

RLock() 與Lock()的區別

我們需要知道Lock()作為一個基本的鎖物件,一次只能一個鎖定,其餘鎖請求,需等待鎖釋放後才能獲取,否則會發生死鎖:

import threading
resource.lock = threading.lock()
resource = 0
resource.lock.acquire()
resource.lock.acquire()
resource += 1
resource.lock.release()
resource.lock.release()

為解決同一執行緒中不能多次請求同一資源的問題,python提供了“可重入鎖”:threading.RLockRLock內部維護著一個Lock和一個counter變數,counter記錄了acquire的次數,從而使得資源可以被多次acquire

直到一個執行緒所有的acquire都被release,其他的執行緒才能獲得資源 。用法和threading.Lock類相同,即比如遞迴鎖的使用:

import threading
lock = threading.RLock()
def dosomething(lock):
    lock.acquire()
    # do something
    lock.release()
lock.acquire()
dosomething(lock)
lock.release()

以上就是Python並行程式設計多執行緒鎖機制Lock與RLock實現執行緒同步的詳細內容,更多關於Python鎖Lock RLock執行緒同步的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com