python - 死简单示例中的线程锁定失败
问题描述
这是最简单的玩具示例。我知道 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()
在锁定示例中,因为我想说明后台作业的想法。但我可以等待线程的活跃性(谢谢弗兰克耶林!),这可以解决锁问题。不过,多算仍然困扰着我。
解决方案
您不会等到所有线程都完成后再查看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(...)
会给你你想要的答案,并负责等待线程。
推荐阅读
- spring-boot - 在 Spring Boot 中从 LDAP 获取用户组
- email - 如果我更改 DNS MX 记录,我会丢失存储在 Webmail 中的现有电子邮件吗?
- flutter - Flutter - 如何获取拖动对象相对于小部件的位置
- python - 使用乘法和减法加密和解密图像
- spring-boot - 如何编写查询spring boot
- python - 每两列绘制一次
- python - 用 Python 更新数据库。Mysql 可以忽略 ' 内容中的样式标记吗?
- python - 如何获取 json 列表的最后一个元素?
- amazon-web-services - 有没有办法在开始在 Elastic Beanstalk 中部署之前等待完成所有 cron 作业
- c++ - #包括
停止在 C++20 中工作?