Sobes.tech
Middle+

Como pode implementar a sincronização em Kotlin usando coroutines?

sobes.tech IA

Resposta da IA

A sincronização em corrotinas Kotlin é alcançada usando primitivas especiais e estruturas de dados da biblioteca kotlinx.coroutines.

Abordagens principais:

  1. Mutex

    • Fornece exclusão mútua para proteger seções críticas do código.
    • A corrotina que tenta obter um Mutex fica bloqueada até que ele seja liberado.
    • Possui funções lock() e unlock(), bem como withLock mais idiomático.
    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) // Espera-se 10000
    }
    
  2. Semaphore

    • Limita o número de corrotinas que podem acessar um recurso ou executar um bloco de código simultaneamente.
    • Gerencia um pool de permissões.
    • Possui funções acquire() e release(), bem como withPermit.
    import kotlinx.coroutines.sync.Semaphore
    import kotlinx.coroutines.sync.withPermit
    import kotlinx.coroutines.*
    
    val semaphore = Semaphore(2) // Até 2 corrotinas ao mesmo tempo
    
    suspend fun doLimitedWork(id: Int) {
        semaphore.withPermit {
            println("Corrotina $id adquiriu uma permissão. Trabalhando...")
            delay(100) // Simulação de trabalho
            println("Corrotina $id liberou uma permissão.")
        }
    }
    
    suspend fun main() = coroutineScope {
        repeat(5) { i ->
            launch {
                doLimitedWork(i)
            }
        }
    }
    
  3. **Operações atômicas (de kotlinx.coroutines.atomic)

    • Oferecem operações seguras para threads sobre tipos primitivos e referências.
    • Utilizam instruções de CPU de baixo nível (CAS - Compare-and-Swap).
    • Adequadas para operações simples sem bloqueios explícitos.
    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) // Espera-se 10000
    }
    
  4. Estruturas de dados partilhadas num despachador de thread único

    • Abordagem mais simples. Executar corrotinas num contexto de thread única (Dispatchers.Default.limitedParallelism(1) ou newSingleThreadContext) garante execução sequencial do código, evitando condições de corrida. Não é uma primitiva de sincronização explícita, mas garante sincronização ao serializar o acesso.
    import kotlinx.coroutines.*
    
    val mySingleThreadContext = newSingleThreadContext("SingleThread")
    var sharedData = mutableListOf<Int>()
    
    suspend fun addToSharedData(value: Int) {
        sharedData.add(value) // Seguro, pois executado numa única thread
    }
    
    suspend fun main() = withContext(mySingleThreadContext) {
        repeat(10_000) {
            launch {
                 addToSharedData(it)
            }
        }
        println(sharedData.size) // Espera-se 10000
    }
    
  5. Canais

    • Não são primitivas de sincronização em si, mas podem ser usados para transferir dados de forma segura entre corrotinas, resolvendo indiretamente problemas de sincronização de acesso aos dados transmitidos.
    • Fornecem uma forma de transferir fluxo de dados de uma corrotina para outra.

A escolha da abordagem depende do cenário de uso. Mutex e Semaphore fornecem mecanismos clássicos de bloqueio, operações atômicas são eficientes para atualizações simples, e o despachador de thread única é conveniente quando o acesso a dados compartilhados deve ser estritamente sequencial. Canais são usados para comunicação e coordenação, não para proteção direta de recursos compartilhados.