第38條 線程中使用Lock來防止數據競爭

Python在內置的threading模塊中提供了Lock類,該類相當於互斥鎖,可以保護各線程數據結構不被破壞。

案例:新建一個Counter類,統計傳感器採樣獲得的樣本數量。

### 計數器
class Counter(object):
    def __init__(self):
        self.count = 0
        
    def increment(self,offset):
        self.count += offset
        
 ### 傳感器
 def woker(sensor_index,how_many,counter):
    for _ in range(how_many):
        ## Read from the sensor
        ##....
        counter.increment(1)
        
 #### run_threads函數
 def run_threads(func,how_many,counter):
    threads = []
    for i in range(5):
        args = (i,how_many,counter)
        thread = Thread(target=func,args = args)
        threads.append(thread)
        thread.start()
    for thread in threads:
        thread.joint()
        
if __name__=='__main__':
    ###執行
    how_many = 10**5
    counter = Counter()
    run_threads(worker,how_many,counter)

    print(5*how_many,counter.count)

輸出結果:

500000 278328

可以看出,兩者數據相差較大,這是由於程序在執行self.count += offset語句時,並非原子操作,而是可以分解爲:

value = getattr(counter,'count')
result = value + offset
setattr(counter,'count',result)

上述三個操作,在任意兩個操作之間都可能發生線程切換,這種頻繁的線程切換,交錯執行可能會令線程把舊的value設置給Counter

因此,在不加鎖的前提下,允許多條線程修改同一對象,那麼程序的數據結構可能會遭到破壞。

我們可以用互斥鎖保護Counter對象,使得多個線程同時訪問value值的時候,不會將該值破壞。同一時刻,只有一個線程能夠獲得這把鎖。如下:

class LockingCounter(object):
    def __init__(self):
        self.lock = Lock()
        self.count = 0
    def increment(self,offset):
        with self.lock:###判斷鎖狀態
            self.count += offset
            
if __name__ == '__main__':
    counter = LockingCounter()
    run_threads(worker,how_many,counter)
    print(5*how_many,counter.count)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章