使用 Mutex 与 Semaphore 管理共享状态
使用 Mutex 保护共享可变状态,并使用 Semaphore 限制并发。
使用 Mutex 与 Semaphore 管理共享状态 是 CoddyKit 上的免费 Kotlin Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Kotlin Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Kotlin Academy 课程共包含 4 节课。
共享状态问题
在多个线程上运行的协程可能会破坏共享的可变状态。如果没有同步,并发递增会产生错误的结果。
import kotlinx.coroutines.*
fun main() = runBlocking {
var counter = 0
val jobs = List(1000) {
launch(Dispatchers.Default) { counter++ } // race condition!
}
jobs.forEach { it.join() }
println("Expected 1000, got: $counter") // likely wrong
}Mutex 基础
Mutex 是用于协程的互斥锁。它会挂起(而不是阻塞)调用方,直到锁可用。
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
val mutex = Mutex()
var counter = 0
val jobs = List(1000) {
launch(Dispatchers.Default) {
mutex.withLock { counter++ } // safe
}
}
jobs.forEach { it.join() }
println("Counter: $counter") // 1000
}withLock 扩展
mutex.withLock { ... } 会获取锁、运行代码块,然后释放锁——即使发生异常或取消也能如此。
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
val mutex = Mutex()
var list = mutableListOf<Int>()
List(10) {
launch {
mutex.withLock {
list.add(it)
}
}
}.forEach { it.join() }
println(list.sorted())
}手动使用 lock / unlock
您可以直接调用 lock() 和 unlock() 进行更细致的控制,但请优先使用 withLock,以避免忘记解锁。
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
val mutex = Mutex()
mutex.lock()
try {
println("Critical section")
} finally {
mutex.unlock() // must always release
}
}Mutex 不可重入
Kotlin 的 Mutex 不可重入。在同一个协程中再次调用 lock() 会导致死锁。请使用计数器或重构代码,以避免嵌套加锁。
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
val mutex = Mutex()
// This deadlocks:
// mutex.withLock { mutex.withLock { println("never") } }
mutex.withLock {
println("Acquired once — safe")
}
}Semaphore 基础
Semaphore(n) 允许最多 n 个协程并发执行。它就像一个拥有 n 个名额的票证系统。
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
val semaphore = Semaphore(3) // max 3 concurrent
List(10) { i ->
launch {
semaphore.withPermit {
println("Concurrent task $i"); delay(100)
}
}
}.forEach { it.join() }
}withPermit 扩展
semaphore.withPermit { ... } 会获取许可、运行代码块并释放许可——这是信号量尝试获取许可的协程安全等价方式。
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
val sem = Semaphore(2)
val results = mutableListOf<String>()
List(5) { i ->
launch {
sem.withPermit {
delay(50)
synchronized(results) { results.add("task-$i") }
}
}
}.forEach { it.join() }
println(results)
}使用 Semaphore 限制速率
使用信号量限制并发接口调用的速率——将同时进行的请求数限制为 N 个。
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
val apiSemaphore = Semaphore(5) // max 5 concurrent requests
suspend fun callApi(id: Int): String {
return apiSemaphore.withPermit {
delay(100) // simulate network
"Response-$id"
}
}
fun main() = runBlocking {
val results = (1..20).map { async { callApi(it) } }.awaitAll()
println("Got ${results.size} responses")
}Mutex 与 Semaphore
Mutex:二进制信号量(1 个许可),用于独占访问。Semaphore(n):具有 n 个许可,用于限制并发量。使用 Mutex 保护资源,使用 Semaphore 限制并行度。
import kotlinx.coroutines.sync.*
// Mutex = Semaphore(1) for exclusive access
// Semaphore(n) = n-way concurrency limit
val exclusive = Mutex() // one at a time
val limited = Semaphore(3) // three at a time
fun main() { println("Mutex for exclusion; Semaphore for rate limiting") }使用 Mutex 实现原子计数器
使用 Mutex 保护递增和读取操作,实现一个线程安全的计数器类。
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
class AtomicCounter {
private val mutex = Mutex()
private var count = 0
suspend fun increment() = mutex.withLock { count++ }
suspend fun get() = mutex.withLock { count }
}
fun main() = runBlocking {
val counter = AtomicCounter()
List(100) { launch { counter.increment() } }.forEach { it.join() }
println("Count: ${counter.get()}")
}替代方案:单线程调度器
您可以不使用互斥锁,而是将可变状态限制在单线程调度器中。该调度器一次只运行一个协程,因此访问天然安全。
import kotlinx.coroutines.*
fun main() = runBlocking {
val singleThread = newSingleThreadContext("CounterThread")
var counter = 0
List(1000) {
launch(singleThread) { counter++ }
}.forEach { it.join() }
println("Counter: $counter") // 1000, no mutex needed
singleThread.close()
}快速检查
Mutex 和 Semaphore 的主要区别是什么?
回顾
Mutex 为共享状态提供独占访问——请优先使用 withLock。Semaphore(n) 将并发量限制为 n。两者都会挂起(而不是阻塞)等待中的协程。对于简单场景,单线程调度器通常更简洁。
常见问题解答
「使用 Mutex 与 Semaphore 管理共享状态」课时是免费的吗?
是的 — 「使用 Mutex 与 Semaphore 管理共享状态」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Kotlin Academy 课程的其余内容,请升级到 CoddyKit PRO。 Kotlin Academy 课程共包含 4 节课。
「使用 Mutex 与 Semaphore 管理共享状态」这节课中我会学到什么?
使用 Mutex 保护共享可变状态,并使用 Semaphore 限制并发。 你通过在浏览器中直接运行的动手代码来练习 Kotlin Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Kotlin Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Kotlin Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「使用 Mutex 与 Semaphore 管理共享状态」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Kotlin Academy 课中编写并运行代码吗?
能。每节 Kotlin Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- Channel 基础:send、receive 与 close
- Channel 类型:会合、缓冲、合并与无限
- 使用 Mutex 与 Semaphore 管理共享状态
- Actor 与结构化状态管理