哪些用例适合Dispatchers。Kotlin中的默认用例?

时间:2020-06-19 10:40:50

标签: multithreading kotlin threadpool kotlin-coroutines

根据文档,IODefault调度程序的线程池大小如下:

  • Dispatchers.Default:默认情况下,此调度程序使用的最大并行度等于CPU内核数,但至少为两个。
  • Dispatchers.IO:默认为64个线程或内核数(以较大者为准)的限制。

除非我缺少一条信息,否则在Default上执行大量CPU密集型工作会更加高效(更快),因为上下文切换的发生频率会降低

但是以下代码实际上在Dispatchers.IO上运行得更快:

fun blockingWork() {
    val startTime = System.currentTimeMillis()
    while (true) {
        Random(System.currentTimeMillis()).nextDouble()
        if (System.currentTimeMillis() - startTime > 1000) {
            return
        }
    }
}

fun main() = runBlocking {
    val startTime = System.nanoTime()
    val jobs = (1..24).map { i ->
        launch(Dispatchers.IO) { // <-- Select dispatcher here
            println("Start #$i in ${Thread.currentThread().name}")
            blockingWork()
            println("Finish #$i in ${Thread.currentThread().name}")
        }
    }
    jobs.forEach { it.join() }
    println("Finished in ${Duration.of(System.nanoTime() - startTime, ChronoUnit.NANOS)}")
}

我正在8核CPU上运行24个作业(因此,我可以使Default调度程序的所有线程保持繁忙状态)。这是我机器上的结果:

Dispatchers.IO --> Finished in PT1.310262657S
Dispatchers.Default --> Finished in PT3.052800858S

你能告诉我我在这里想念什么吗?如果IO工作得更好,为什么我应该使用IO以外的任何调度程序(或具有很多线程的任何线程池)。

1 个答案:

答案 0 :(得分:2)

回答您的问题:Default调度程序最适合不具有阻塞功能的任务,因为在同时执行此类工作负载时the-difference-between-concurrent-and-parallel-execution不会超过最大并行度。

https://www.cs.uic.edu/~jbell/CourseNotes/OperatingSystems/5_CPU_Scheduling.html


您的实验有缺陷。如评论中已经提到的,您的blockingWork不受CPU限制,但受IO限制。这与等待有关-任务被阻止并且CPU无法执行其后续指令的时间段。本质上,您的blockingWork只是“等待1000毫秒”,并行等待1000毫秒X次比顺序执行要快。您执行了一些计算(生成随机数-本质上也可能是IO绑定),但是如上所述,您的工作人员正在生成或多或少的这些数字,具体取决于将基础线程置于休眠状态的时间。

我进行了一些简单的实验,生成了斐波那契数(通常用于模拟CPU工作量)。但是,在考虑了JVM中的JIT之后,我无法轻松地产生任何证明Default调度程序性能更好的结果。可能是上下文切换没有人们想象的那么重要。可能是因为调度程序没有使用IO调度程序为我的工作量创建更多线程。可能是我的实验也有缺陷。不能确定-在JVM上进行基准测试本身并不简单,并且将协程(及其线程池)添加到混合中肯定不会使其变得更简单。

但是,我认为这里还有一些更重要的考虑因素,那就是阻止Default调度员对阻止呼叫更敏感。随着池中线程的减少,所有线程都更有可能被阻塞,并且此时没有其他协程可以执行。

您的程序正在线程中运行。如果所有线程都被阻止,则您的程序没有执行任何操作。创建新线程非常昂贵(通常是内存方式),因此对于具有阻塞功能的高负载系统而言,这成为一个限制因素。 Kotlin在引入“挂起”功能方面做得非常出色。程序的并发性不仅限于您拥有的线程数。如果一个流需要等待,它只是挂起而不是阻塞线程。但是,“世界并不完美”,并非所有事情都“悬而未决”-仍然存在“阻塞”调用-您确定使用的 no 库在幕后执行此类调用吗?拥有权利的同时也被赋予了重大的责任。对于协程,需要特别注意死锁,尤其是在使用Default调度程序时。实际上,我认为IO调度程序应该是默认调度程序。