英文:
Why Kotlin/Java doesn't have an option for preemptive scheduler?
问题
重型CPU绑定任务可能会阻塞线程并延迟其他等待执行的任务。这是因为JVM无法中断正在运行的线程,需要程序员的帮助和手动中断。
因此,在Java/Kotlin中编写CPU绑定任务需要手动干预,以使事情顺利进行,例如在下面的代码中使用Kotlin中的Sequence
。
fun simple(): Sequence<Int> = sequence { // sequence builder
for (i in 1..3) {
Thread.sleep(100) // 假装我们在计算
yield(i) // 生成下一个值
}
}
fun main() {
simple().forEach { value -> println(value) }
}
就我所理解的原因是,具有能够中断运行线程的抢占式调度程序会增加性能开销。
但是,是否有一个开关会更好,这样您可以选择?如果您想要以更快的非抢占式调度程序运行JVM,或者以较慢的抢占式(在N个指令后中断并切换线程)运行JVM,但能够顺利运行而不需要手动干预呢?
我想知道为什么Java/Kotlin没有这样的JVM开关,可以允许选择所需的模式。
英文:
Heavy CPU bound task could block the thread and delay other tasks waiting execution. That's because JVM can't interrupt running thread and require help from programmer and manual interruption.
So writing CPU bound tasks in Java/Kotlin requires manual intervention to make things run smoothly, like using Sequence
in Kotlin in code below.
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) }
}
As far as I understood the reason is that having preemptive scheduler with the ability to interrupt running threads have performance overhead.
But wouldn't it be better to have a switch, so you can choose? If you would like to run JVM with faster non-preemptive scheduler. Or with slower pre-emtpive (interrupting and switching the tread after N instructions) one but able to run things smoothly and don't require manual labor to do that?
I wonder why Java/Kotlin doesn't have such JVM switch that would allow to choose what mode you would like.
答案1
得分: 6
使用Kotlin协程或Java虚拟线程(在Loom之后),您可以从操作系统中获得抢占式调度。
按照通常的做法,未被阻塞的任务(即它们需要CPU)会在Kotlin默认调度程序或Java ForkJoinPool中的真实操作系统线程上进行多路复用。这些操作系统线程由操作系统抢占式调度。
然而,与旧式多线程不同,当任务在等待I/O时被阻塞时,它们不会分配给线程。从抢占的角度来看,这没有任何区别,因为等待I/O的任务无法抢占另一个正在运行的任务。
然而,当使用协程编程时,您无法同时获得对大量任务的抢占式调度。如果有许多需要CPU的任务,那么前N个任务将分配给一个真实的线程,并由操作系统对它们进行时间分片。其余的任务将在队列中等待,直到前N个任务完成。
但在现实生活中,当您有10000个需要同时进行交互的任务时,它们都是I/O绑定的任务。平均而言,任何时候都不会有太多任务需要CPU,因此从默认调度程序或ForkJoinPool中获得的真实线程数量已经足够。在正常操作中,等待线程的任务队列几乎总是空的。
如果您真的面临10000个需要同时进行CPU密集型交互的任务的情况,那么无论如何,您都会感到沮丧,因为时间分片不会提供非常平滑的体验。
英文:
When you program using Kotlin coroutines or Java virtual threads (after Loom), you get preemptive scheduling from the OS.
Following usual practices, tasks that are not blocked (i.e., they need CPU) are multiplexed over real OS threads in the Kotlin default dispatcher or Java ForkJoinPool. Those OS threads are scheduled preemptively by the OS.
Unlike old-style multithreading, however, tasks are not assigned to a thread when they are blocked waiting for I/O. This makes no difference in terms of preemption, since a task that is waiting for I/O couldn't possibly preempt another running task anyway.
What you don't get when programming with coroutines, is preemptive scheduling over a large number of tasks simultaneously. If you have many tasks that require the CPU, then the first N will be assigned to a real thread and the OS will time slice them. The remaining ones will wait in the queue until those ones are done.
But in real life, when you have 10000 tasks that need to be simultaneously interactive, they are I/O bound tasks. On average, there aren't many that require the CPU at any one time, so the number of real threads you get from the default dispatcher or ForkJoinPool is plenty. In normal operation, the queue of tasks waiting for threads is almost always empty.
If you really had a situation where 10000 CPU-bound tasks needed to be simultaneously interactive, well, then you would be sad anyway, because time slicing would not provide a very smooth experience.
答案2
得分: 5
这个问题基于错误的前提:在JVM中,抢占式调度器是唯一的选择。 没有现代的JVM使用协作式多任务处理。
现代的JVM没有实现用户空间线程或自己的调度器。JVM使用本地操作系统线程。本地线程由操作系统调度,并且操作系统调度器是抢占式的。
JVM线程与本地操作系统线程的一对一映射对于需要高并发性的应用程序是一个问题。线程相对稀缺且昂贵。为了解决这个问题,Project Loom正在研究添加“虚拟线程”,这可能允许更节省地使用本地线程,特别是用于I/O绑定任务。
Project Loom正在积极开发中,目前没有确切的时间表来将其纳入标准的Java中。关于Project Loom如何调度“虚拟线程”,Project Loom的最新更新(2020年5月)声称*“虚拟线程是抢占式的,而不是协作式的”,但随后又指出“JDK中的调度器没有一个当前使用基于时间片的虚拟线程抢占”*。从目前的状态来看,“虚拟线程”调度器在Project Loom中既不完全是协作式的,也不完全是抢占式的。很有趣的是,等待项目的发展以及它融入主流Java时会得到什么样的结果。
在7月28日的Q&A中,Loom项目负责人Ron Pressler提到您将能够为虚拟线程插入自己的调度器,但没有详细说明您对调度算法有多少控制权。
英文:
This question is based on a false premise: In the JVM, a preemptive scheduler is your only choice. No modern JVM uses co-operative multitasking.
No modern JVM implements user space threads or a scheduler of its own. JVMs use native operating system threads instead. Native threads are scheduled by the operating system, and operating system schedulers are preemptive.
The fact that JVM threads map 1-to-1 to native operating system threads is a problem for applications that need a high level of concurrency. Threads are relatively scarce and expensive. To address this, Project Loom is investigating adding "virtual threads" that may allow using native threads more sparingly, especially for I/O bound tasks.
Project Loom is under active development and there is no set schedule of when it will become a part of standard Java. Regarding how Project Loom schedules "virtual threads", the latest (May 2020) update from Project Loom claims "virtual threads are preemptive, not cooperative" but then goes on to say "none of the schedulers in the JDK currently employs time-slice-based preemption of virtual threads". It sounds like in its current state the "virtual thread" scheduler in Project Loom is somewhere between fully co-operative and fully pre-emptive. It will be interesting to see how the project develops and what we will get when it is integrated into main stream Java.
In a July 28 Q&A the Loom project lead Ron Pressler mentioned that you will be able to plug in a scheduler of your own for virtual thread, but did not go into details of how much control you get over the scheduling algorithm.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论