为什么 Kotlin/Java 没有抢占式调度程序选项?

Why Kotlin/Java doesn't have an option for preemptive scheduler?

重 CPU 绑定任务可能会阻塞线程并延迟其他等待执行的任务。那是因为JVM不能中断运行ning线程,需要程序员的帮助和手动中断。

因此,在 Java/Kotlin 中编写 CPU 绑定任务需要手动干预才能使事情 运行 顺利进行,例如在下面的代码中在 Kotlin 中使用 Sequence

fun simple(): Sequence<Int> = sequence { // sequence builder
    for (i in 1..3) {
        Thread.sleep(100) // pretend we are computing it
        yield(i) // yield next value
    }
}

fun main() {
    simple().forEach { value -> println(value) } 
}

据我了解,原因是拥有能够中断 运行ning 线程的抢占式调度程序会带来性能开销。

但是如果有一个开关,你可以选择,不是更好吗?如果您想 运行 JVM 具有更快的非抢占式调度程序。或者使用较慢的先发制人(在 N 条指令后中断和切换胎面)但能够 运行 事情顺利进行并且不需要体力劳动来做到这一点?

我想知道为什么 Java/Kotlin 没有这样的 JVM 开关可以让你选择你想要的模式。

这个问题基于一个错误的前提:在 JVM 中,抢占式调度程序是您唯一的选择。没有现代 JVM 使用co-operative多任务。

没有现代 JVM 实现用户 space 线程或它自己的调度程序。 JVM 改为使用本机操作系统线程。本机线程由操作系统调度,操作系统调度器抢占式的。

JVM 线程 1 对 1 映射到本机操作系统线程这一事实对于需要高级别并发性的应用程序来说是一个问题。线程相对稀缺且昂贵。为了解决这个问题,Project Loom 正在研究添加“虚拟线程”,这可能允许更谨慎地使用本机线程,特别是对于 I/O 绑定任务。

Loom 项目正在积极开发中,没有确定何时成为标准的一部分 Java。关于 Loom 项目如何安排“虚拟线程”,Loom 项目的 latest (May 2020) update 声称 “虚拟线程是抢占式的,而不是合作的” 但接着又说 “JDK 中的 none 个调度程序当前使用 time-slice-based 虚拟线程抢占”。听起来 Project Loom 中的“虚拟线程”调度程序在其当前状态下介于完全 co-operative 和完全 pre-emptive 之间。看看这个项目是如何发展的,以及当它被集成到主流中时我们会得到什么将会很有趣Java。

July 28 Q&A the Loom project lead Ron Pressler 中提到您可以为虚拟线程插入您自己的调度程序,但没有详细说明您对调度算法的控制程度。

当您使用 Kotlin 协程或 Java 虚拟线程(在 Loom 之后)进行编程时,您 获得 来自 OS 的抢占式调度。

按照惯例,未阻塞的任务(即,它们需要 CPU)在 Kotlin 默认调度程序或 Java ForkJoinPool 中的真实 OS 线程上进行多路复用。那些 OS 线程被 OS.

抢占式调度

然而,与 old-style 多线程不同的是,任务在阻塞等待 I/O 时不会分配给线程。这使得在抢占方面没有区别,因为正在等待I/O的任务不可能抢占另一个运行任务。

在使用协程编程时,您不会得到的是同时对大量任务进行抢占式调度。如果您有许多任务需要 CPU,那么第一个 N 将被分配给一个真正的线程,而 OS 将对它们进行时间分割。其余的将在队列中等待,直到完成。

但在现实生活中,当您有 10000 个任务需要同时 交互 时,它们是 I/O 绑定任务。平均而言,没有多少人在任何时候都需要 CPU,因此您从默认调度程序或 ForkJoinPool 获得的实际线程数很多。 在正常运行中,等待线程的任务队列几乎总是空的。

如果你真的有10000个CPU-bound个任务需要同时交互的情况,好吧,那你无论如何都会难过,因为时间切片不会提供非常流畅的体验。