这是最简单的玩具示例。我知道并发。未来和更高级别的代码;我之所以选择玩具示例,是因为我正在教玩具示例(与高级教材相同)。
它从不同的线程开始计数,我得到...好吧,这里甚至更奇怪。通常我得到的计数器少于我应有的数量(例如5M),通常少得多,例如20k。但是随着我减少循环次数,在1000左右的数量上,它一直是正确的。然后在某个中间数字处,我得到几乎正确的,偶尔是正确的,但偶尔会比nthread x nloop的乘积稍大。我 am 在Jupyter单元中反复运行它,但是第一行实际上应该将计数器重置为零,而不是保留任何旧的总数。
lock = threading.Lock()
counter, nthread, nloop = 0, 100, 50_000
def increment(n, lock):
global counter
for _ in range(n):
lock.acquire()
counter += 1
lock.release()
for _ in range(nthread):
t = Thread(target=increment, args=(nloop, lock))
t.start()
print(f"{nloop:,} loops X {nthread:,} threads -> counter is {counter:,}")
如果我添加.join()
,则行为会更改,但仍然不正确。例如,在不尝试锁定的版本中:
counter, nthread, nloop = 0, 100, 50_000
def increment(n):
global counter
for _ in range(n):
counter += 1
for _ in range(nthread):
t = Thread(target=increment, args=(nloop,))
t.start()
t.join()
print(f"{nloop:,} loops X {nthread:,} threads -> counter is {counter:,}")
# --> 50,000 loops X 100 threads -> counter is 5,022,510
确切的溢价有所不同,但我反复看到类似的情况。
在锁示例中,我并不是真的想.join()
,因为我想说明后台作业的想法。但是我可以等待线程的存活(谢谢弗兰克·耶林!),这可以解决锁定问题。不过,超额交易仍然困扰着我。
答案 0 :(得分:2)
在查看counter
之前,您不必等待所有线程完成。这就是为什么您这么快就得到结果的原因。
threads = []
for _ in range(nthread):
t = threading.Thread(target=increment, args=(nloop, lock))
t.start()
threads.append(t)
for thread in threads:
thread.join()
print(f"{nloop:,} loops X {nthread:,} threads -> counter is {counter:,}")
打印出预期的结果。
50,000 loops X 100 threads -> counter is 5,000,000
已更新。我强烈建议改用ThreadPoolExecutor(),它会为您跟踪线程。
with ThreadPoolExecutor() as executor:
for _ in range(nthread):
executor.submit(increment, nloop, lock)
print(...)
将为您提供所需的答案,并负责等待线程。