Sobes.tech
Middle+

Por que não é recomendado usar blocos de sincronização e construções semelhantes ao trabalhar com corrotinas?

sobes.tech IA

Resposta da IA

A utilização de construções bloqueantes, como synchronized ou lock da biblioteca padrão, ao trabalhar com corutinas leva a um uso ineficiente dos recursos e pode causar problemas.

Razões principais:

  • Bloqueio de thread: Os blocos de sincronização param a execução do thread, não apenas das corutinas. Se uma função Suspending for executada dentro de um bloco sincronizado, o thread ficará bloqueado até que a função Suspending termine e o bloqueio seja liberado. Isso pode levar a uma "fome" de outras corutinas que usam o mesmo thread.
  • Incompatibilidade com funções Suspending: O mecanismo de trabalho das corutinas (troca de contexto, continuações) não considera os bloqueios ao nível de threads. Executar uma função Suspending dentro de um bloco sincronizado viola a natureza não bloqueante das corutinas.
  • Violação dos princípios de concorrência estruturada: As corutinas têm seu próprio sistema de marcas de cancelamento e hierarquia. Os bloqueios ao nível de thread ignoram esse sistema, dificultando o manejo correto de cancelamentos e gerenciamento do ciclo de vida.
  • Risco de deadlocks: A combinação de bloqueios de threads e mecanismos de concorrência de corutinas aumenta a probabilidade de deadlocks complexos, difíceis de depurar.

Em vez de bloqueios ao nível de thread para sincronizar o acesso a recursos compartilhados em corutinas, deve-se usar primitivas de concorrência especializadas da biblioteca kotlinx.coroutines:

  • Mutex: Garante exclusão mútua. A corutina que tenta adquirir um mutex ocupado se suspende (sem bloquear o thread) até que o mutex seja liberado.
  • Semaphore: Limita a quantidade de corutinas que têm acesso simultâneo a um recurso.
  • Channel: Usado para trocar dados entre corutinas.
  • Variáveis atômicas: Para operações atômicas simples.

Exemplo de uso de 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 {
        // Seção crítica
        counter++
    }
}

fun main() = runBlocking {
    // Criar múltiplas corutinas para acesso concorrente
    repeat(1000) {
        launch {
            incrementCounter()
        }
    }
    delay(1000) // Dar tempo às corutinas para executarem
    println("Counter: $counter") // Espera-se 1000
}

mutex.withLock é uma função suspending. Se o mutex estiver ocupado, a corutina que chamou withLock se suspende e libera o thread para outras tarefas. Quando o mutex fica disponível, a corutina é retomada. Isso não bloqueia o thread, ao contrário de synchronized.