首页 > 解决方案 > 死简单示例中的线程锁定失败

问题描述

这是最简单的玩具示例。我知道 concurrent.futures 和更高级别的代码;我选择玩具示例是因为我正在教它(作为与高级内容相同的材料的一部分)。

它从不同的线程踩到柜台,我得到......好吧,这里更奇怪。通常我得到的计数器少于我应该得到的(例如 5M),通常比 20k 少得多。但是当我减少循环的数量时,在 1000 这样的数字上它始终是正确的。然后在某个中间数字处,我几乎是正确的,偶尔是正确的,但偶尔会比 nthread x nloop 的乘积略大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()在锁定示例中,因为我想说明后台作业的想法。但我可以等待线程的活跃性(谢谢弗兰克耶林!),这可以解决锁问题。不过,多算仍然困扰着我。

标签: pythonpython-3.xthread-safety

解决方案


您不会等到所有线程都完成后再查看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(...)

会给你你想要的答案,并负责等待线程。


推荐阅读