0Pricing
Kotlin Academy · Lezione

Mutex e Semaphore per lo stato condiviso

Protegga lo stato mutabile condiviso con Mutex e limiti la concorrenza con Semaphore.

Mutex e Semaphore per lo stato condiviso è una lezione Kotlin Academy gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Kotlin Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Kotlin Academy include 4 lezioni in totale.

Il problema dello stato condiviso

Le coroutine eseguite su più thread possono corrompere lo stato mutabile condiviso. Senza sincronizzazione, gli incrementi concorrenti producono risultati errati.

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
}

Nozioni di base su Mutex

Mutex è un blocco di mutua esclusione per le coroutine. Sospende il chiamante (non lo blocca) finché il blocco non è disponibile.

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
}

Estensione withLock

mutex.withLock { ... } acquisisce il blocco, esegue il blocco di codice e lo rilascia, anche in caso di eccezione o annullamento.

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 manualmente

È possibile chiamare direttamente lock() e unlock() per un controllo più preciso, ma è preferibile usare withLock per evitare di dimenticare di sbloccare.

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 non è rientrante

Mutex di Kotlin NON è rientrante. Chiamare di nuovo lock() dalla stessa coroutine provoca un deadlock. Utilizzi un contatore oppure ristrutturi il codice per evitare blocchi annidati.

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")
    }
}

Nozioni di base su Semaphore

Un Semaphore(n) consente a un massimo di n coroutine di procedere in modo concorrente. È simile a un sistema di biglietti con n posti.

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() }
}

Estensione withPermit

semaphore.withPermit { ... } acquisisce un permesso, esegue il blocco e lo rilascia: è l'equivalente sicuro per le coroutine del tentativo di acquisizione di un semaforo.

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)
}

Limitazione della frequenza con Semaphore

Utilizzi un semaforo per limitare il numero di chiamate API concorrenti: consenta un massimo di N richieste alla volta.

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 e Semaphore a confronto

Mutex: semaforo binario (1 permesso), per l'accesso esclusivo. Semaphore(n): n permessi, per una concorrenza limitata. Utilizzi Mutex per proteggere una risorsa e Semaphore per limitare il parallelismo.

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") }

Contatore atomico con Mutex

Implementi una classe contatore thread-safe utilizzando Mutex per proteggere gli incrementi e le letture.

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()}")
}

Alternative: dispatcher a thread singolo

In alternativa a un mutex, confini lo stato mutabile a un dispatcher a thread singolo. Su quel dispatcher viene eseguita una sola coroutine alla volta, rendendo l'accesso sicuro per sua natura.

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()
}

Controllo rapido

Qual è la differenza principale tra Mutex e Semaphore?

Riepilogo

Mutex fornisce accesso esclusivo allo stato condiviso: è preferibile usare withLock. Semaphore(n) limita la concorrenza a n coroutine. Entrambi sospendono (non bloccano) le coroutine in attesa. Nei casi semplici, spesso è più pulito utilizzare un dispatcher a thread singolo.

Domande Frequenti

La lezione «Mutex e Semaphore per lo stato condiviso» è gratuita?

Sì — il testo completo di «Mutex e Semaphore per lo stato condiviso» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Kotlin Academy, passa a CoddyKit PRO. Il corso Kotlin Academy include 4 lezioni in totale.

Cosa imparerò in «Mutex e Semaphore per lo stato condiviso»?

Protegga lo stato mutabile condiviso con Mutex e limiti la concorrenza con Semaphore. Eserciti Kotlin Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Kotlin Academy?

Non è richiesta alcuna esperienza precedente. Kotlin Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «Mutex e Semaphore per lo stato condiviso»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Kotlin Academy?

Sì. Ogni lezione Kotlin Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Basi dei Channel: send, receive e close
  2. Tipi di Channel: Rendezvous, Buffered, Conflated, Unlimited
  3. Mutex e Semaphore per lo stato condiviso
  4. Actor e gestione strutturata dello stato
← Torna a Kotlin Academy