前一个帖子沉了,再问一次,关于 python 的 ThreadPoolExecutor 使用,是我理解错了吗?

2016-07-26 10:01:46 +08:00
 snachx

前几天问了一次,很快沉了,再问问试试...

看 concurrent.futures 的文档,讲 ThreadPoolExecutor 的时候有一段

def wait_on_future():
    f = executor.submit(pow, 5, 2)
    # This will never complete because there is only one worker thread and
    # it is executing this function.
    print(f.result())

executor = ThreadPoolExecutor(max_workers=1)
executor.submit(wait_on_future)

看注释的意思应该是说如果 max_workers > 1 的话就不会有问题吧?可是为什么我把 max_workers 设为大于 1 的值, f 的状态还是一直卡在 pending 没输出结果呢? macOS 和 Linux 上都试过了。

2968 次点击
所在节点    Python
18 条回复
IMRES
2016-07-26 10:20:19 +08:00
这个文档中的关于"deadlocks"的一个反面的例子……
需要将最后一行改为`wait_on_future()`才是正确的。
gnuth
2016-07-26 10:43:06 +08:00
https://github.com/python/cpython/blob/master/Lib/concurrent/futures/thread.py#L104-L114

wait_on_future 里面的 submit 卡在获取 _shutdown_lock 上了。
snachx
2016-07-26 10:50:26 +08:00
@IMRES 我知道这里是讲 deadlocks ,但是根据注释,这个例子强调的是因为只有一个 worker ,所以会死锁,如果最后一行改为 wait_on_future(),即使 max_workers = 1 也不会有问题的啊
yangtukun1412
2016-07-26 10:55:09 +08:00
在 executor.submit(wait_on_future) 后面加上:

while True:
time.sleep(1)


原因: 可以看下 concurrent/futures/thread.py 中的 _python_exit() 和 _worker() 两个函数.
snachx
2016-07-26 11:01:30 +08:00
@gnuth 试着调试了一下,并没有卡在获取_shutdown_lock 啊
gnuth
2016-07-26 11:27:47 +08:00
@snachx 晕,我想错了。。
SuperFashi
2016-07-26 12:05:14 +08:00
所以真的是为什么……我设的 worker 为 2 顺利运行啊……
SuperFashi
2016-07-26 12:05:46 +08:00
楼上的都没有自己试一下吗?
snachx
2016-07-26 12:15:21 +08:00
@SuperFashi 我设置为 1 就不行

用 @yangtukun1412 的方法睡一秒就行,不用循环,在想为什么
snachx
2016-07-26 12:16:15 +08:00
@SuperFashi 是不管设为几都不行,而且注释的意思是设为 1 肯定不行,不理解了
ljbha007
2016-07-26 12:20:53 +08:00
除了线程的锁 python 还有一个全局锁 GIL 可能跟这有关系 但是我不没用过这个类 所以不清楚
SuperFashi
2016-07-26 12:28:12 +08:00
@snachx
上图。
snachx
2016-07-26 12:43:08 +08:00
@SuperFashi 我试了一下在 python shell 下面这么做确实没有问题,你试试写到文件里面,然后 python xxx.py 呢,你在 python shell 下面做跟前面说的睡一秒应该是一个意思吧
yangtukun1412
2016-07-26 13:15:13 +08:00
@snachx 大概说下吧

1. 设 executor.submit(wait_on_future) 启动的是线程 1, executor.submit(pow, 5, 2) 启动的是线程 2.
2. executor.submit(wait_on_future) 是非阻塞的, 所以在执行后主线程会退出.
3. 由于 thread.py 中注册了 _python_exit(), 所以会在主线程退出前执行这个函数, 1) 设置 _shutdown = True, 2) 向队列中 put 一个 None, 3) 阻塞在 t.join(sys.maxint) 这里等待全部子线程退出.
4. 线程 1 阻塞在 _worker() 中 work_item.run() 这一行, 因此 t.join(sys.maxint) 也会阻塞住, 阻止主线程退出.
5. 线程 2 从队列中 get 到的并不是 pow 函数对应的 work_item, 而是 _python_exit() 中 put 的 None, 再加上 shutdown == True, 所以线程 2 会不做任何操作直接退出.
6. 由于线程 2 已经退出, 所以 pow 函数没有被执行, 因此线程 1 也就永远被阻塞住了.

所以出现这个问题的原因就是主线程提前退出, 用 sleep 等方式阻止主线程退出就可以解决了, 你改成 executor.submit(wait_on_future).result() 也是一样 OK 的.
SuperFashi
2016-07-26 13:17:44 +08:00
@snachx @yangtukun1412
这位说得清晰明了,当然我推荐阻塞方式为 executor.shutdown(wait=True)
SuperFashi
2016-07-26 13:19:59 +08:00
啊 记错了 抱歉 用 result 阻塞就好了
snachx
2016-07-26 13:39:44 +08:00
@yangtukun1412
之前已经知道了是主线程提前退出造成的,不过还没搞清楚具体流程。现在明白了,非常感谢~
IMRES
2016-07-26 14:30:40 +08:00
@snachx 不好意思 是我搞错了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/294943

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX