Goroutines vs asyncio 任务 + CPU-bound 调用的线程池

Posted

技术标签:

【中文标题】Goroutines vs asyncio 任务 + CPU-bound 调用的线程池【英文标题】:Goroutines vs asyncio tasks + thread pool for CPU-bound calls 【发布时间】:2017-05-30 22:34:33 【问题描述】:

goroutines 是否大致等同于 python 的 asyncio 任务,还有一个附加特性是任何 CPU 绑定任务都被路由到 ThreadPoolExecutor 而不是添加到事件循环中(当然,假设我们使用没有 GIL 的 python 解释器)?

我缺少的两种方法之间有什么实质性区别吗?当然,除了并发是 Go 不可或缺的一部分所带来的效率和代码清晰度之外。

【问题讨论】:

“任何受 CPU 限制的任务都被路由到它自己的线程”这不是 goroutines 的工作方式,所以我不确定该部分的问题是什么。 我认为 go runtime 会动态更改线程数以确保 goroutine 不会相互阻塞。由于事件循环已经可以在不阻塞的情况下处理 I/O 密集型任务,但不能处理 CPU 密集型任务,我得出的结论是,等效的处理方法是将 CPU 密集型任务放在它们自己的线程中。也许我应该说“路由到线程池”而不是“路由到自己的线程”?我编辑了这个问题。 【参考方案1】:

我想我知道部分答案。我试图总结我对asyncio任务和goroutines之间差异的理解,按重要性排序:

1) 与asyncio 不同,人们很少需要担心他们的 goroutine 会阻塞太久。 OTOH,goroutine 之间的内存共享类似于线程之间的内存共享,而不是 asyncio 任务,因为 goroutine 执行顺序保证要弱得多(即使硬件只有一个核心)。

asyncio 只会在显式的awaityield 和某些事件循环方法上切换上下文,而 Go 运行时可能会开启更微妙的触发器(例如某些函数调用)。所以asyncio 是完全合作的,而 goroutine 只是大部分合作(路线图表明随着时间的推移它们会变得更不合作)。

一个非常紧密的循环(例如数字计算)仍然可能阻塞 Go 运行时(嗯,它正在运行的线程)。如果发生这种情况,它的影响将比在 python 中小——除非它发生在多个线程中。

2) Goroutines 对并行计算有现成的支持,这需要asyncio 下的更复杂的方法。

Go 运行时可以并行运行线程(如果有多个内核可用),因此它有点类似于在无 GIL 的 Python 运行时下的线程池中运行多个 asyncio 事件循环,并带有语言感知负载均衡器在前面。

3) Go 运行时会在单独的线程中自动处理阻塞的系统调用;这需要在asyncio 下明确完成(例如,使用run_in_executor)。

也就是说,就内存成本而言,goroutine 非常类似于 asyncio 任务而不是线程。

【讨论】:

【参考方案2】:

我想你可以认为它在下面是这样工作的,当然。这不是很准确,但足够接近。

但是有一个很大的不同:在 Go 中你可以编写直线代码,所有的 I/O 阻塞都会自动为你处理。您可以用简单的直线代码调用 Read,然后 Write,然后 Read。据我了解,使用 Python asyncio,您需要排队一个函数来处理读取,而不仅仅是调用 Read。

【讨论】:

谢谢!我的自我回答是否有任何重大误解?是的,在 python 中,您要么需要使用异步 IO 库,要么如果它不可用,则需要将 IO 操作显式发送到线程池(这可以通过返回未来来简化,这样它就不会中断异步代码风格)。对同步阻塞 IO 的天真调用会挂起事件循环。 Go 运行时确实会根据需要启动新线程。在将 goroutine 与异步 I/O 函数进行比较时,我不确定该说什么。它们确实不同,所以当你说这两种方法是等价的时,我会说只有在非常松散的解释下才是正确的。 不,在 python asyncio 中,您可以执行类似 await stream_reader.read(num_bytes) 的操作,这与阻塞世界中的 file.read(num_bytes 并没有太大区别

以上是关于Goroutines vs asyncio 任务 + CPU-bound 调用的线程池的主要内容,如果未能解决你的问题,请参考以下文章

Python asyncio 任务排序

asyncio - 多次等待协程(周期性任务)

在 asyncio 中批量处理任务

Python 3.6 asyncio - 从未检索到任务异常 - 任务的产量不好:200

asyncio:只能在前一个任务达到预定义阶段时启动任务吗?

如何在 asyncio 中同时运行任务?