Python threading.Lock未按互斥锁预期工作,求技术解答
问题
我一直认为threading.Lock对象可作为互斥锁(mutex),用于避免Python多线程脚本中的竞态条件,但实际测试后发现两种可能:1. 我的认知错误(与多年经验相悖);2. Python 2.7-3.9版本存在相关Bug。理论上,只要用Lock保护临界区(即执行值增量的代码),多线程共享值的增量操作就不会有问题,但运行以下代码后,发现互斥锁未达到预期效果,恳请各位解惑:
#!/usr/bin/env python from __future__ import print_function import sys import threading import time Stop = False class T(threading.Thread): def __init__(self,list_with_int): self.mycount = 0 self.to_increment = list_with_int super(T,self).__init__() def run(self,): while not Stop: with threading.Lock(): self.to_increment[0] += 1 self.mycount += 1 intList = [0] t1 = T(intList) t2 = T(intList) t1.start() t2.start() Delay = float(sys.argv[1]) if sys.argv[1:] else 3.0 time.sleep(Delay) Stop = True t1.join() t2.join() total_internal_counts = t1.mycount + t2.mycount print("Compare:\n\t{total_internal_counts}\n\t{intList[0]}\n".format(**locals())) assert total_internal_counts == intList[0]
解答
问题出在你每次循环都新建了一个threading.Lock实例,两个线程用的根本不是同一个锁,自然起不到互斥作用。
原代码里with threading.Lock()这行,每次循环都会生成全新的锁对象,相当于两个线程各自拿自己的锁,完全没限制对方访问临界区,竞态条件当然会出现,导致intList[0]的数值比total_internal_counts小。
修复很简单:在主线程创建一个共享锁,传给两个线程,让它们用同一个锁保护临界区。修改后的代码如下:
#!/usr/bin/env python from __future__ import print_function import sys import threading import time Stop = False class T(threading.Thread): def __init__(self, list_with_int, lock): self.mycount = 0 self.to_increment = list_with_int self.lock = lock # 保存共享锁 super(T, self).__init__() def run(self): while not Stop: with self.lock: # 使用共享锁 self.to_increment[0] += 1 self.mycount += 1 intList = [0] shared_lock = threading.Lock() # 创建共享锁实例 t1 = T(intList, shared_lock) t2 = T(intList, shared_lock) t1.start() t2.start() Delay = float(sys.argv[1]) if sys.argv[1:] else 3.0 time.sleep(Delay) Stop = True t1.join() t2.join() total_internal_counts = t1.mycount + t2.mycount print("Compare:\n\t{total_internal_counts}\n\t{intList[0]}\n".format(**locals())) assert total_internal_counts == intList[0]
修改后,两个线程会争抢同一个锁,临界区的增量操作被正确保护,intList[0]和total_internal_counts会始终相等,断言不会触发。
另外说明:Python 2.7到3.9版本的threading.Lock本身没有这类功能Bug,问题完全是代码实现错误,和版本无关。
内容的提问来源于stack exchange,提问作者Daniel Moore
相关产品推荐
相关产品推荐

