Concorrência e Multithreading em Kotlin: Nível Avançado
No desenvolvimento moderno, o gerenciamento eficaz da concorrência é a chave para criar aplicações rápidas e responsivas. Kotlin, graças ao suporte integrado a coroutines, oferece um conjunto de ferramentas poderoso e flexível que vai muito além das threads padrão do Java. Neste artigo, exploraremos conceitos avançados de concorrência e multithreading em Kotlin, incluindo o trabalho com Channel, Flow, Mutex e os detalhes dos agendadores.
1. Coroutines: Mais Fundo que o Básico
Coroutines são threads leves que permitem escrever código assíncrono em estilo imperativo. No entanto, em nível avançado, é importante entender como funcionam os dispatchers e os contextos de coroutines.
Agendadores (Dispatchers)
A escolha do dispatcher correto é crítica para o desempenho:
Dispatchers.Default— para tarefas intensivas em CPU (por padrão, usa um pool de threads igual ao número de núcleos do processador).Dispatchers.IO— para operações de entrada/saída (rede, arquivos). O pool de threads pode se expandir até 64 threads (ou mais, dependendo da configuração).Dispatchers.Main— para a thread da UI (Android, JavaFX).newSingleThreadContextenewFixedThreadPoolContext— para criar pools próprios.
import kotlinx.coroutines.*
fun main() = runBlocking { launch(Dispatchers.Default) { println("Tarefa intensiva em CPU em ${Thread.currentThread().name}") } launch(Dispatchers.IO) { println("Tarefa de E/S em ${Thread.currentThread().name}") }}Concorrência Estruturada
Uma das principais características do Kotlin é a concorrência estruturada. Ela garante que as coroutines filhas sejam concluídas antes da coroutine pai. Isso evita vazamentos de memória e simplifica o gerenciamento do ciclo de vida.
fun main() = runBlocking { coroutineScope { launch { delay(1000) println("Tarefa 1 concluída") } launch { delay(500) println("Tarefa 2 concluída") } } println("Todas as tarefas concluídas")}2. Canais (Channel) para Transferência de Dados entre Coroutines
Channel é o equivalente a uma fila bloqueante (BlockingQueue), mas para coroutines. Ele permite transferir dados com segurança entre coroutines, suspendendo o remetente se o canal estiver cheio, ou o destinatário se o canal estiver vazio.
Tipos de Canais
- RendezvousChannel (padrão) — não possui buffer. O envio é bloqueado até o recebimento.
- BufferedChannel — possui um buffer de tamanho definido.
- ConflatedChannel — armazena apenas o último valor enviado, sobrescrevendo os anteriores.
- UnlimitedChannel — buffer ilimitado (cuidado: pode causar OOM).
import kotlinx.coroutines.*import kotlinx.coroutines.channels.*
fun main() = runBlocking { val channel = Channel<Int>(capacity = 5) // Canal com buffer
launch { for (i in 1..10) { channel.send(i) println("Enviado: $i") } channel.close() }
launch { for (value in channel) { println("Recebido: $value") delay(100) } }}Fan-out e Fan-in
Canais são excelentes para os padrões "fan-out" (um produtor, vários consumidores) e "fan-in" (vários produtores, um consumidor).
3. Flow: Fluxos de Dados Reativos
Ao contrário do Channel, que é "quente" (os dados são enviados independentemente da existência de um assinante), o Flow é um fluxo "frio" que