【发布时间】:2016-01-29 15:29:58
【问题描述】:
我正在创建一个threading.Timer(2,work) 运行线程。在每个工作函数内部,在某些情况下,全局计数器必须递增,而不会在派生的工作线程之间访问计数器变量发生冲突。
我试过Queue.Queue 分配的计数器以及threading.Lock()。这是实现线程安全的全局增量变量的最佳方式。
【问题讨论】:
标签: python multithreading
我正在创建一个threading.Timer(2,work) 运行线程。在每个工作函数内部,在某些情况下,全局计数器必须递增,而不会在派生的工作线程之间访问计数器变量发生冲突。
我试过Queue.Queue 分配的计数器以及threading.Lock()。这是实现线程安全的全局增量变量的最佳方式。
【问题讨论】:
标签: python multithreading
不确定您是否已经尝试过这种特定的语法,但对我来说这一直很有效:
定义一个全局锁:
import threading
threadLock = threading.Lock()
然后你必须在每次增加单个线程中的计数器时获取和释放锁:
with threadLock:
global_counter += 1
【讨论】:
Lock(和RLock,以及任何threading 类实例)作为with 上下文。代码更具可读性。
一种解决方案是使用multiprocessing.Lock 保护计数器。你可以把它放在一个类中,像这样:
from multiprocessing import Process, RawValue, Lock
import time
class Counter(object):
def __init__(self, value=0):
# RawValue because we don't need it to create a Lock:
self.val = RawValue('i', value)
self.lock = Lock()
def increment(self):
with self.lock:
self.val.value += 1
def value(self):
with self.lock:
return self.val.value
def inc(counter):
for i in range(1000):
counter.increment()
if __name__ == '__main__':
thread_safe_counter = Counter(0)
procs = [Process(target=inc, args=(thread_safe_counter,)) for i in range(100)]
for p in procs: p.start()
for p in procs: p.join()
print (thread_safe_counter.value())
上面的 sn-p 最初取自 Eli Bendersky 的博客,here。
【讨论】:
self.value += 1 应该是原子的。还是我误解了什么?
self.value += 1 不是相关意义上的单个 python 操作,因为它编译成多个字节码。实际上是tmp1 = self.value; tmp2 = tmp1.__iadd__(1); self.value = tmp2,这三个语句中的每一个都是原子的,但整个序列绝对不是。
如果您使用的是 CPython1,则无需显式锁定即可:
import itertools
class Counter:
def __init__(self):
self._incs = itertools.count()
self._accesses = itertools.count()
def increment(self):
next(self._incs)
def value(self):
return next(self._incs) - next(self._accesses)
my_global_counter = Counter()
我们需要两个计数器:一个用于计数增量,一个用于计数value() 的访问。这是因为itertools.count 没有提供访问 current 值的方法,只有 next 值。所以我们需要通过请求值来“撤消”我们产生的增量。
这是线程安全的,因为 itertools.count.__next__() 在 CPython 中是原子的(感谢 GIL!),我们不会保留差异。
请注意,如果value() 被并行访问,确切的数字可能不会完全稳定。它可以是正比于访问的线程数的余量。理论上,self._incs 可以在一个线程中首先更新,而self._accesses 在另一个线程中首先更新。但总体而言,系统不会因未保护的写入而丢失任何数据;它总是会稳定到正确的值。
1 并非所有 Python 都是 CPython,但很多(大多数?)是。
2 感谢https://julien.danjou.info/atomic-lock-free-counters-in-python/ 最初的想法是使用itertools.count 来增加和更正第二个访问计数器。他们没把所有的锁都拆掉就停了下来。
【讨论】: