0Pricing
Kotlin Academy · レッスン

共有状態のための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フィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Channelの基本:send、receive、close
  2. Channelの種類:Rendezvous、Buffered、Conflated、Unlimited
  3. 共有状態のためのMutexとSemaphore
  4. Actorと構造化された状態管理
← Kotlin Academyに戻る