Middle+
Come si può implementare la sincronizzazione in Kotlin utilizzando le coroutine?
sobes.tech AI
Risposta dell'AI
La sincronizzazione nelle coroutine di Kotlin si ottiene utilizzando primitive e strutture dati speciali della libreria kotlinx.coroutines.
Approcci principali:
-
Mutex
- Fornisce esclusione mutua per proteggere le sezioni critiche del codice.
- La coroutine che tenta di ottenere un Mutex si blocca fino a quando non viene rilasciato.
- Ha funzioni
lock()eunlock(), oltre awithLockpiù idiomatico.
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.* val mutex = Mutex() var counter = 0 suspend fun increment() { mutex.withLock { counter++ } } suspend fun main() = coroutineScope { repeat(10_000) { launch { increment() } } println(counter) // Si aspetta 10000 } -
Semaphore
- Limita il numero di coroutine che possono accedere a una risorsa o eseguire un blocco di codice contemporaneamente.
- Gestisce un pool di permessi.
- Ha funzioni
acquire()erelease(), oltre awithPermit.
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.* val semaphore = Semaphore(2) // Fino a 2 coroutine contemporaneamente suspend fun doLimitedWork(id: Int) { semaphore.withPermit { println("Coroutine $id acquisito un permesso. Lavorando...") delay(100) // Simulazione di lavoro println("Coroutine $id rilasciato un permesso.") } } suspend fun main() = coroutineScope { repeat(5) { i -> launch { doLimitedWork(i) } } } -
**Operazioni atomiche (da
kotlinx.coroutines.atomic)- Offrono operazioni thread-safe su tipi primitivi e riferimenti.
- Utilizzano istruzioni CPU di basso livello (CAS - Compare-and-Swap).
- Adatte per operazioni semplici senza blocchi espliciti.
import kotlinx.coroutines.atomic.AtomicInt import kotlinx.coroutines.* val atomicCounter = AtomicInt(0) suspend fun atomicIncrement() { atomicCounter.incrementAndGet() } suspend fun main() = coroutineScope { repeat(10_000) { launch { atomicIncrement() } } println(atomicCounter.value) // Si aspetta 10000 } -
Strutture dati condivise su un dispatcher a thread singolo
- Approccio più semplice. Eseguire coroutine in un contesto a thread singolo (
Dispatchers.Default.limitedParallelism(1)onewSingleThreadContext) garantisce l'esecuzione sequenziale del codice, evitando condizioni di gara. Non è una primitive di sincronizzazione esplicita, ma garantisce la sincronizzazione serializzando l'accesso.
import kotlinx.coroutines.* val mySingleThreadContext = newSingleThreadContext("SingleThread") var sharedData = mutableListOf<Int>() suspend fun addToSharedData(value: Int) { sharedData.add(value) // Sicuro, poiché eseguito su un solo thread } suspend fun main() = withContext(mySingleThreadContext) { repeat(10_000) { launch { addToSharedData(it) } } println(sharedData.size) // Si aspetta 10000 } - Approccio più semplice. Eseguire coroutine in un contesto a thread singolo (
-
Canali
- Non sono primitive di sincronizzazione in sé, ma possono essere usati per trasferire dati in modo sicuro tra coroutine, risolvendo indirettamente problemi di sincronizzazione dell'accesso ai dati trasmessi.
- Forniscono un modo per trasferire flussi di dati da una coroutine all'altra.
La scelta dell'approccio dipende dallo scenario di utilizzo. Mutex e Semaphore forniscono meccanismi classici di blocco, le operazioni atomiche sono efficienti per aggiornamenti semplici, e il dispatcher a thread singolo è conveniente quando l'accesso ai dati condivisi deve essere strettamente sequenziale. I canali sono usati per comunicazione e coordinamento, non per proteggere direttamente le risorse condivise.