协程是抢占式的还是只是阻塞了选择 Runnable 的线程?

Are Coroutines preemptive or just blocking the thread that picked the Runnable?

在深入了解协程调度程序(例如“默认”和“IO”)的实现之后, 我看到它们只包含一个 Java 执行器(这是一个简单的线程池)和一个 Runnables 队列,它们是协程逻辑块。

让我们举一个例子,我在同一个协程上下文中启动 10,000 个协程,例如“默认”调度程序,它包含一个在其池中有 512 个真实线程的执行器。

这些协程将被添加到调度程序队列中(以防运行中的协程数量超过最大阈值)。

例如,假设我在 10,000 个协程中启动的前 512 个协程非常缓慢和沉重。

我的其余协程是否会被阻塞,直到我的至少 1 个真实线程完成, 或者在那些“用户-space线程”中是否有一些时间片机制?

协程是协同调度的,而不是抢先调度的,因此上下文切换只能在挂起点进行。这实际上是设计使然,它使执行速度更快,因为协程不会互相争斗,上下文切换的次数也比抢占式调度少。

但是正如您所注意到的,它有缺点。如果执行长时间 CPU 密集型计算,建议不时调用 yield()。它允许为其他协程释放线程。另一种解决方案是为我们的计算创建一个不同的线程池,以将它们与应用程序的其他部分分开。这与抢占式调度有类似的缺点 - 它会使 coroutines/threads 争夺 CPU 个核心的访问权。

一旦协程开始执行,它将继续执行直到遇到挂起点,这是通过调用 suspendCoroutine or suspendCancellableCoroutine.

引入的

暂停是基本思想

然而这是设计使然,因为挂起是协同程序引入的性能提升的基础,协同程序背后的全部意义在于 为什么在线程什么都不做时继续阻塞线程但请稍等(前同步 IO)。为什么不用这个线程做点别的事情

如果没有暂停,您将失去很多性能提升

因此,为了在您的特定情况下识别开关,您必须定义术语 slow and heavy。 cpu 密集型任务(例如生成质数)可能会很慢且繁重,而 API 在服务器上执行复杂计算然后 returns 结果的调用也可能很慢且繁重。如果 512 个协程没有暂停点,那么其他人将不得不等待它们完成。这实际上打败了使用协程的全部意义,因为您有效地使用协程作为线程的替代品,但增加了开销。

如果你必须并行执行一堆非挂起操作,你应该使用像 Executor 这样的服务,因为在这种情况下协程除了添加一个无用的层之外什么都不做抽象。