You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何GlobalScope.async性能优于coroutineScope.async?

Kotlin协程作用域与CPU密集型任务并行问题解答

问题描述

我正在开发一个小型Kotlin应用(基于Spring Boot),但对协程作用域理解不足,搞不懂异步协程返回结果的性能问题。

初始代码(串行执行,性能差)

@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())
        }
        // ... 展示结果 ...
    }
    
    // 纯阻塞式CPU密集型桌游模拟,无异步、等待或挂起操作
    fun executeLadderRound(players: List<Player>): List<Results>
}

这段代码仅占用约10%的CPU,耗时25秒,异步任务似乎是串行执行(移除async后耗时相同)。

修改后代码(并行执行,性能提升)

@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())
        }
        // ... 展示结果 ...
    }
    
    // 纯阻塞式CPU密集型桌游模拟,无异步、等待或挂起操作
    fun executeLadderRound(players: List<Player>): List<Results>
}

性能大幅提升(耗时降至5秒)且CPU占用100%,符合预期。但疑问是:run作为主方法,默认作用域难道不是GlobalScope吗?

问题解答

1. runBlocking的作用域≠GlobalScope

runBlocking创建的是绑定当前线程的协程作用域,默认调度器不会启用线程池。而GlobalScope.async默认使用Dispatchers.Default调度器,该调度器会根据CPU核心数创建线程池,专门适配CPU密集型任务的并行执行。

2. 初始代码串行的核心原因

在runBlocking作用域中调用不带调度器参数的async时,async会继承当前作用域的调度器。由于executeLadderRound是纯阻塞方法,没有任何挂起点,协程无法让出当前线程,导致所有async任务必须串行执行在同一个线程上,CPU利用率自然极低,耗时和不用async完全一致。

3. 更优的实现方式(避免GlobalScope)

直接使用GlobalScope不推荐,因为它的生命周期不受控,容易引发内存泄漏。正确的做法是给async显式指定Dispatchers.Default调度器:

async(Dispatchers.Default) { executeLadderRound(players.shuffled()) }

这样既利用线程池并行执行CPU密集型任务,充分压榨CPU性能,又能保证协程作用域可控,避免GlobalScope带来的潜在问题。

内容的提问来源于stack exchange,提问作者darklurker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 17:05:01