启动多协程为何出现竞态条件?值异常及执行顺序疑问
协程多线程环境下变量计数错误的原因解析
一、为什么变量x的结果不正确?
核心问题是并发修改引发的竞态条件。++x看似是单一操作,但底层会拆成三个独立步骤:
- 读取当前x的数值
- 对读取到的值执行加1操作
- 将加1后的结果写回x
当使用Dispatchers.Default时,这些协程会被分配到线程池的多个线程中并行执行。比如两个协程同时执行第一步,都读到x=0,接着各自加1得到1,最后都把1写回x——这就导致两次增量操作只让x增加了1,而非预期的2。一万次这类操作累积下来,最终x的值自然会远小于10000。
二、协程的“顺序”到底指什么?
你说的“协程按顺序创建”是对的:repeat(10_000)会依次创建每一个协程,这个创建过程在runBlocking所在线程是串行执行的。但创建协程≠立即执行协程代码块。
每个创建好的协程会被放入Dispatchers.Default的调度队列,等待线程池中的空闲线程调度执行。线程池的调度是无序的,哪个协程先执行、由哪个线程执行,完全由调度策略决定。所以协程的创建是顺序的,但执行是并发且无序的,多个协程的代码块可能在不同线程同时运行,或交替抢占CPU执行,这就给竞态条件创造了发生的机会。
三、如何修复这个问题?
有几种常见的解决方式:
- 使用原子类:把
var x = 0替换为val x = AtomicInteger(0),用x.incrementAndGet()代替++x,原子操作能保证读、改、写的整个过程不可分割。 - 使用互斥锁:用
Mutex保护增量操作,确保同一时间只有一个协程能执行++x:fun main() = runBlocking { var x = 0 val mutex = Mutex() repeat(10_000) { launch(Dispatchers.Default) { mutex.withLock { ++x } } } delay(1.seconds) println(x) } - 串行执行增量:如果不需要并行,可将所有增量操作放到同一个协程中,或用
channel串行处理任务,从根源避免并发修改。
内容的提问来源于stack exchange,提问作者Ken Kiarie
相关产品推荐
相关产品推荐

