kotlin 为什么GlobalScope.async看起来比coroutineScope.async性能更好

eyh26e7m  于 2023-03-09  发布在  Kotlin
关注(0)|答案(1)|浏览(141)

我目前正在开发一个小应用程序来学习Kotlin(使用一些Sping Boot 特性来加快工作速度)。不幸的是,我不能把我的头缠在异步运行的协程返回一些结果上,很可能是由于对协程作用域的理解不好。
下面的代码是我的“main”方法

@Component
class GameRunner() : CommandLineRunner {

    override fun run(vararg args: String) = runBlocking {

        val players = listOf(Player(1),Player(2),Player(3),Player(4))
        val results = mutableListOf<Results>()
        val measureTimeMillis = measureTimeMillis {
            val deferredResults = mutableListOf<Deferred<List<Results>>>()
            for (i in 0 until 250) {
                deferredResults.add(async { executeLadderRound(players.shuffled()) })
            }
            results.addAll(deferredResults.awaitAll().flatten())
        }
        // ... present the results ...
    }
    
    // for the record, this method plays a simulation of a board game that is fully blocking CPU intensive process, there are no async calls, waits or suspensions of any kind
    fun executeLadderRound(players: List<Player>): List<Results>

它使用了我大约10%的CPU,并在25秒内完成。感觉即使我很难启动大量异步作业,它们似乎是按顺序执行的(如果我完全删除异步,时间是相同的)。
但是,如果我这样做:

@Component
class GameRunner() : CommandLineRunner {

    override fun run(vararg args: String) = runBlocking {

        val players = listOf(Player(1),Player(2),Player(3),Player(4))
        val results = mutableListOf<Results>()
        val measureTimeMillis = measureTimeMillis {
            val deferredResults = mutableListOf<Deferred<List<Results>>>()
            for (i in 0 until 250) {
                deferredResults.add(GlobalScope.async { executeLadderRound(players.shuffled()) })
            }
            results.addAll(deferredResults.awaitAll().flatten())
        }
        // ... present the results ...
    }
    
    // for the record, this method plays a simulation of a board game that is fully blocking CPU intensive process, there are no async calls, waits or suspensions of any kind
    fun executeLadderRound(players: List<Player>): List<Results>

它看起来和我预期的完全一样。性能有了很大的提升(降低到5秒),并且使用了100%的CPU。但是为什么呢?如果run是我的“main”方法,那么默认情况下它不应该是GlobalScope吗?

cotxawn7

cotxawn71#

由于您使用的是runBlocking,因此默认的线程调度器是单线程调度器,正如the documentation中针对JVM版本所述:
此构建器的默认CoroutineDispatcher是事件循环的内部实现,它处理此阻塞线程中的延续,直到此协程完成。
因为您使用的作用域是单线程的,所以您用async创建的所有子协程都必须共享同一个线程,因此它们不能并行运行。
当您使用GlobalScope而不是提供给runBlocking协程lambda的内部CoroutineScope来启动它们时,它们不是子协程。它们是使用GlobalScope的默认调度程序Dispatchers创建的无父协程。默认,它有多个线程,因此它们可以并行运行。
您可以为runBlocking指定调度程序以避免此问题:

override fun run(vararg args: String) = runBlocking(Dispatchers.Default) {
    //...
}

顺便说一下,您可以使用map运算符来简化代码:

override fun run(vararg args: String) = runBlocking(Dispatchers.Default) {
    val players = (1..4).map(::Player)
    val results = (0 until 250).map { async { executeLadderRound(players.shuffled()) }
        .awaitAll()
        .flatten()

    // ... present the results ...
}

相关问题