Sobes.tech
Middle+

Perché non è consigliabile usare blocchi di sincronizzazione e strutture simili quando si lavora con le coroutine?

sobes.tech AI

Risposta dell'AI

L'uso di strutture bloccanti come synchronized o lock della libreria standarde, quando si lavora con le coroutine, porta a un uso inefficiente delle risorse e può causare problemi.

Motivi principali:

  • Blocco del thread: I blocchi di sincronizzazione fermano l'esecuzione del thread, non solo delle coroutine. Se all'interno di un blocco sincronizzato viene eseguita una funzione Suspending, il thread sarà bloccato finché la funzione Suspending non termina e il lock viene rilasciato. Questo può portare a una "fame" di altre coroutine che usano lo stesso thread.
  • Incompatibilità con funzioni Suspending: Il meccanismo di lavoro delle coroutine (cambio di contesto, continuazioni) non considera i blocchi a livello di thread. Eseguire una funzione Suspending all'interno di un blocco sincronizzato viola la natura non bloccante delle coroutine.
  • Violazione dei principi di concorrenza strutturata: Le coroutine hanno il loro sistema di segnali di cancellazione e gerarchie. I lock a livello di thread ignorano questo sistema, rendendo difficile la gestione corretta delle cancellazioni e del ciclo di vita.
  • Rischio di deadlock: La combinazione di lock a livello di thread e meccanismi di concorrenza delle coroutine aumenta la probabilità di deadlock complessi, difficili da debug.

Invece di usare lock a livello di thread per sincronizzare l'accesso alle risorse condivise nelle coroutine, si dovrebbero usare primitive di concorrenza specializzate della libreria kotlinx.coroutines:

  • Mutex: Garantisce esclusione mutua. La coroutine che tenta di acquisire un mutex occupato si sospende (senza bloccare il thread) fino a quando il mutex non viene rilasciato.
  • Semaphore: Limita il numero di coroutine che hanno accesso simultaneo a una risorsa.
  • Channel: Usato per scambiare dati tra coroutine.
  • Variabili atomiche: Per operazioni atomiche semplici.

Esempio di utilizzo di Mutex:

import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.*

val mutex = Mutex()
var counter = 0

suspend fun incrementCounter() {
    mutex.withLock {
        // Sezione critica
        counter++
    }
}

fun main() = runBlocking {
    // Creiamo molte coroutine per accesso concorrente
    repeat(1000) {
        launch {
            incrementCounter()
        }
    }
    delay(1000) // Diamo tempo alle coroutine di eseguire
    println("Counter: $counter") // Si aspetta 1000
}

mutex.withLock è una funzione suspending. Se il mutex è occupato, la coroutine che chiama withLock si sospende e libera il thread per altri compiti. Quando il mutex diventa disponibile, la coroutine riprende. Questo non blocca il thread, a differenza di synchronized.