Kotlin 协程面试题:使用 Dispachers.IO 要注意什么

前言

在 Review 代码时经常看到这样的写法

withContext(Dispatchers.IO) {
    // 一些非UI操作
}

代码本意是想在把一些非UI任务放到后台,避免阻塞UI。但有时候开发者会忽略任务类型(CPU 密集型 or IO 密集型),无脑切到 IO 去做是不合理的。

Dispatchers.IO 的原理

顾名思义,Dispatchers.IO 是用于 IO 任务的协程调度器。IO 任务是一种不会给 CPU 带来高负载的工作,大部分时间都消耗在等待硬件响应(来自持久存储或远程主机)上

看一下源码:

// Dispatchers.IO
internal object DefaultIoScheduler : ExecutorCoroutineDispatcher(), Executor {

    private val default = UnlimitedIoScheduler.limitedParallelism(
        SystemProp(
            IO_PARALLELISM_PROPERTY_NAME,
            64.coerceAtLeast(AVAILABLE_PROCESSORS)
        )
    )
   ...
}

这里我们可以看到,在一般情况下,IO 调度器中的最大线程数是 64。如果我们在这个线程池上执行大量占用 CPU 的工作,对于 4 核或 8 核的 CPU 来说,这个数量可能就太多了

private object UnlimitedIoScheduler : CoroutineDispatcher() {

    @InternalCoroutinesApi
    override fun dispatchYield(context: CoroutineContext, block: Runnable) {
        DefaultScheduler.dispatchWithContext(block, BlockingContext, true)
    }

    override fun dispatch(context: CoroutineContext, block: Runnable) {
        DefaultScheduler.dispatchWithContext(block, BlockingContext, false)
    }
   ...
}

这里所有任务都是通过 BlockingContext 进行调度的,内部实现如下:

if (isBlockingTask) {
    // Dispatchers.IO 恒为 true
    signalBlockingWork(stateSnapshot)
} else {
    signalCpuWork()
}

对于 Dispatchers.IO,这个标志始终为 true,这意味着这个调度器是为阻塞调用优化的,用它来处理占用 CPU 密集型任务反而有损性能。

Dispatchers.IO 处理 CPU 任务会影响性能

通过上面代码可知:Dispatchers.IO 默认线程数较多,是弹性线程池,一般取 64 或 CPU 核心数中较高者。CPU 密集型任务主要靠 CPU 运算,过多线程会让 CPU 频繁切换线程上下文,增加额外开销,性能降低。比如有个计算任务,本来一个线程就能高效完成,结果 64 个线程抢着做,CPU 在这 64 个线程间切换,耗费大量时间在上下文切换,真正计算时间变少 。

Dispatchers.IO 专为 I/O 密集型任务设计,I/O 任务大部分时间在等外部资源响应,如网络请求等待服务器回复、文件读写等待硬盘传输数据。它针对这种等待场景优化,对于 CPU 密集型任务这种持续占用 CPU 计算资源的场景,没有优化适配,使用时效率不高。

Dispchers.Default 更适合做 CPU 任务

Dispatchers.Default 使用固定大小线程池,线程数量和 CPU 核心数对应。CPU 密集型任务在多核 CPU 上可并行执行多个线程或进程提升性能,其线程数与核心数匹配,能充分利用 CPU 核心,避免线程过多导致上下文切换开销大的问题。比如 8 核 CPU,Dispatchers.Default 线程池有 8 个线程,刚好每个核心分配一个线程执行计算任务,高效利用 CPU 资源 。

它专门为 CPU 密集型任务设计,任务调度等机制都是围绕 CPU 密集型任务特点优化的。相比 Dispatchers.IO 那种为 I/O 等待场景设计的调度器,Dispatchers.Default 在处理 CPU 密集型任务时,更能发挥 CPU 性能,提升任务执行效率 。

使用三方库时的注意

大家在实现诸如网络请求、读写文件、数据库查询等任务时,都会找各种三方库帮忙。像网络请求,常用 Retrofit 搭配 OkHttp;数据库操作呢,就用 Room。这些库已经在内部处理好了 IO 任务调用,用它们自己的线程来管理。

例如,我们调用 Retrofit 接口中用于 REST API 的挂起函数。OkHttp 内部已经有自己的 DispatcherThreadPoolExecutor 来管理网络调用。所以,如果你把调用包裹在 withContext (Dispatchers.IO) 中,你只是把诸如准备请求和解析 JSON 这类占用 CPU 的工作交给了这个调度器,反而是画蛇添足。而所有真正的阻塞式 IO 其实是在 OkHttp 专用的线程池中进行的。

结论

Dispatchers.IO 处理占用 CPU 的任务会影响应用程序的性能,需要谨慎使用,在 IO 三方库都能自行处理任务调度的情况下,大多数情况下,如果你想在后台线程做些工作,应该使用 Dispatchers.Default

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fundroid

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值