kotlin协程并发安全

90 阅读2分钟

多个协程修改同一个变量,协程并发不安全例如:

class Test {
    suspend fun massiveRun(action: suspend () -> Unit) {
        val n = 100  // 启动的协程数量
        val k = 1000 // 每个协程重复执行同一动作的次数
        val time = measureTimeMillis {
            coroutineScope { // 协程的作用域
                repeat(n) {
                    launch {
                        repeat(k) { action() }
                    }
                }
            }
        }
        Log.d(TAG, "Completed ${n * k} actions in $time ms")
    }

    /**
     * 初始值
     */
    var counter = 0

    fun main() = runBlocking {
        withContext(Dispatchers.Default) {
            massiveRun {// 多个协程协程修改同一个变量,存在线程安全问题:导致和实际结果不一致。
                counter++
            }
        }
        Log.d(TAG, "Counter = $counter")
    }
}
// 输出:Completed 100000 actions in 23 ms
// Counter = 79374

通过线程安全类AtomicInteger,原子操作,保证并发安全。

/**
 * 线程安全的类
 */
var counter = AtomicInteger(0)

fun main() = runBlocking {
    withContext(Dispatchers.Default) {
        massiveRun {// 通过安全操作方法来更新计数器
            counter.incrementAndGet()
        }
    }
    Log.d(TAG, "Counter = $counter")
}
输出:Completed 100000 actions in 22 ms
Counter = 100000

通过互斥锁Mutex

var counter = 0
/**
 * 互斥锁
 */
val mutex = Mutex()
fun main() = runBlocking {
    withContext(Dispatchers.Default) {
        massiveRun {// 通过安全操作方法来更新计数器
            mutex.withLock {
                counter++
            }
        }
    }
    Log.d(TAG, "Counter = $counter")
}
输出:Completed 100000 actions in 345 ms
Counter = 100000

性能低,耗时明显增大。 MutableStateFlow

通过Channel实现安全并发

// 通过Channel
private val counterChannel = Channel<Int>(Channel.UNLIMITED)
suspend fun getCounter(): Int {
    var counter = 0
    for (value in counterChannel) {
        counter += value
    }
    return counter
}
fun main() = runBlocking {
    withContext(Dispatchers.Default) {
        massiveRun {
            counterChannel.send(1)
        }
        counterChannel.close()
    }
    val counter = getCounter()
    Log.d(TAG, "Counter = ${counter}")
}
输出:
Completed 100000 actions in 103 ms
Counter = 100000

耗时明显增加,

/**
 * 协程中的一个并发安全的、可观察的状态容器
 */
val counterFlow = MutableStateFlow(0)
fun main() = runBlocking {
    withContext(Dispatchers.Default) {
        massiveRun {
            counterFlow.update { currentValue -> currentValue + 1 } // 使用 update 函数进行原子更新
        }
    }
    Log.d(TAG, "Counter = ${counterFlow.value}")
}
输出:
Completed 100000 actions in 53 ms
Counter = 100000

launch单线程调度

/**
 * 在单一线程中操作
 */
val singleThreadContext = newSingleThreadContext("SingleThread")
suspend fun massiveRun(action: suspend () -> Unit) {
    val n = 100  // 启动的协程数量
    val k = 1000 // 每个协程重复执行同一动作的次数
    val time = measureTimeMillis {
        coroutineScope { // 协程的作用域
            repeat(n) {
                launch(singleThreadContext) {
                    repeat(k) { action() }
                }
            }
        }
    }
    Log.d(TAG, "Completed ${n * k} actions in $time ms")
}

var counter = 0
fun main() = runBlocking {
    withContext(Dispatchers.Default) {
        massiveRun {
            counter++;
        }
    }
    Log.d(TAG, "Counter = ${counter}")
}
输出:
Completed 100000 actions in 22 ms
Counter = 100000

总结:

方案优点缺点适用场景大概耗时
线程安全数据结构简单高效,直接使用适用范围有限,仅支持固定结构简单计数器22ms
Mutex灵活,适合复杂临界区可能导致性能下降,存在锁竞争多协程并发修改同一数据345ms
Channel生产-消费模型复杂度高,可能影响性能消息队列、事件传递103ms
StateFlow简化状态管理性能有限,高频操作不适用UI状态同步53ms
单线程调度器逻辑简单性能不高严格顺序依赖场景22ms