第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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章