【发布时间】:2017-09-07 23:31:19
【问题描述】:
此代码旨在说明多线程代码如何踩踏其共享变量
#python3.6
from concurrent.futures import ThreadPoolExecutor
def worker(counts, counter):
counts.append(counter)
for i in range(10):
counter = 0
counts = []
with ThreadPoolExecutor(max_workers=4) as executor:
while len(counts) < 1000:
executor.submit(worker, counts, counter)
counter += 1
print("counter = {} length counts = {} max(counts) = {}".
format(counter, len(counts), max(counts)))
典型的输出是:
counter = 1217 length counts = 1216 max(counts) = 1215
counter = 1209 length counts = 1185 max(counts) = 1184
counter = 1124 length counts = 1124 max(counts) = 1123
counter = 1339 length counts = 1338 max(counts) = 1337
counter = 1179 length counts = 1178 max(counts) = 1177
counter = 1032 length counts = 1002 max(counts) = 1001
counter = 1001 length counts = 1000 max(counts) = 999
counter = 1001 length counts = 1000 max(counts) = 999
counter = 1201 length counts = 1201 max(counts) = 1200
counter = 1306 length counts = 1304 max(counts) = 1304
我希望看到长度和最大值与 1000 的小偏差,但 999 和 1500 之间的数字是正常的。
考虑到 while 块应该在 counts 达到 999 长度时完成,并且 append 操作应该是线程安全的,为什么结果会有这么大的差异?我预计会有小错误,而不是这些。
【问题讨论】:
-
因为工人不会立即被执行。他们将在未来的某个时间运行,但你不知道什么时候。在检查循环条件之前,您不能指望它们被执行。相反,您的代码将继续启动工作程序,直到运行足够多的工作以将 1,000 个事物放入列表中。但是,已经提交了更多任务,但还没有机会运行。您可以将计数器的值添加到打印语句中吗?这会让您知道实际提交了多少任务。
-
补充一点:不能保证这甚至会终止。如果线程池永远不会获得 CPU 份额,则可以永远运行循环(或者更确切地说,直到队列内存不足)。
-
仅供参考,
list.append()可以是线程安全的。请参阅Python FAQ。 -
由于线程化的
worker函数有效地改变了counts列表的长度,使用该列表的长度来控制while循环体的执行并不是一个可靠的方法确定要进行多少次executor.submit()调用——因为之前对其的调用可能已经执行,也可能尚未执行worker(并更新了列表的长度)。这可以解释为什么counter中的值比您明显预期的要高。 -
@jszakmeister "此代码旨在说明多线程代码如何踩踏其共享变量"?
标签: python multithreading theory