共有状態のためのMutexとSemaphore
Mutexで共有可変状態を保護し、Semaphoreで同時実行数を制限します。
「共有状態のためのMutexとSemaphore」はCoddyKit上の無料Kotlin Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応の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 { ... }は許可を取得してブロックを実行し、解放します。セマフォのtry-acquireに相当する、コルーチン安全な方法です。
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によるレート制限
セマフォを使ってAPI呼び出しの同時実行数を制限し、一度に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()}")
}代替案:単一スレッドのディスパッチャー
Mutexの代わりに、可変状態を単一スレッドのディスパッチャーに閉じ込める方法があります。そのディスパッチャーでは一度に1つのコルーチンだけが実行されるため、アクセスが本質的に安全になります。
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」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Kotlin Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Kotlin Academyコースには全4レッスンが含まれています。
「共有状態のためのMutexとSemaphore」で何を学びますか?
Mutexで共有可変状態を保護し、Semaphoreで同時実行数を制限します。 ブラウザで直接実行するハンズオンコードでKotlin Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Kotlin Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのKotlin Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「共有状態のためのMutexとSemaphore」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このKotlin Academyレッスンでコードを書いて実行できますか?
はい。すべてのKotlin Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- Channelの基本:send、receive、close
- Channelの種類:Rendezvous、Buffered、Conflated、Unlimited
- 共有状態のためのMutexとSemaphore
- Actorと構造化された状態管理