Concorrência e Multithreading em Go: Nível Avançado
\n\nConcorrência (concurrency) é uma das principais características da linguagem Go, incorporada em sua arquitetura no nível do runtime e da sintaxe. Ao contrário de muitas outras linguagens, onde o multithreading é implementado por meio de bibliotecas complexas e requer gerenciamento manual de threads, o Go oferece goroutines leves e canais (channels). Em um nível avançado, não basta apenas iniciar várias tarefas; é necessário sincronizá-las adequadamente, evitar condições de corrida e utilizar os recursos do sistema de forma eficiente. Neste artigo, abordaremos técnicas avançadas de trabalho com concorrência em Go.
\n\n1. Goroutines: Gerenciamento do Ciclo de Vida e Prioridades
\n\nGoroutines são threads leves gerenciadas pelo runtime do Go. Elas são iniciadas com a palavra-chave go. No entanto, em um nível avançado, é preciso entender como controlar sua execução e finalização.
Inicialização básica de uma goroutine:
\npackage main\n\nimport (\n "fmt"\n "time"\n)\n\nfunc worker(id int) {\n fmt.Printf("Worker %d starting\\n", id)\n time.Sleep(time.Second)\n fmt.Printf("Worker %d done\\n", id)\n}\n\nfunc main() {\n for i := 1; i <= 5; i++ {\n go worker(i)\n }\n time.Sleep(2 * time.Second) // Gambiarra: espera a conclusão\n}\n\nProblema: neste exemplo, usamos time.Sleep para esperar, o que não é confiável. Em um nível avançado, são utilizados canais e sync.WaitGroup.
2. Sincronização com sync.WaitGroup
\n\nsync.WaitGroup permite aguardar a conclusão de um número determinado de goroutines. É uma ferramenta básica, mas poderosa, para sincronização.
package main\n\nimport (\n "fmt"\n "sync"\n "time"\n)\n\nfunc worker(id int, wg *sync.WaitGroup) {\n defer wg.Done() // Decrementa o contador ao finalizar\n fmt.Printf("Worker %d starting\\n", id)\n time.Sleep(time.Second)\n fmt.Printf("Worker %d done\\n", id)\n}\n\nfunc main() {\n var wg sync.WaitGroup\n for i := 1; i <= 5; i++ {\n wg.Add(1) // Incrementa o contador antes de iniciar\n go worker(i, &wg)\n }\n wg.Wait() // Bloqueia até o contador zerar\n fmt.Println("All workers completed")\n}\n\nImportante: wg.Add() deve ser chamado antes de iniciar a goroutine, caso contrário, pode ocorrer uma condição de corrida. Use defer wg.Done() dentro da goroutine para garantir a decrementação do contador, mesmo em caso de pânico.
3. Canais: Bufferização, Direção e Select
\n\nCanais são o principal mecanismo de troca de dados entre goroutines. Em um nível avançado, são utilizados canais com buffer, canais unidirecionais e o operador select.
3.1. Canais com Buffer
\nCanais com buffer permitem enviar dados sem recebimento imediato. Isso reduz bloqueios e aumenta a taxa de transferência.
\npackage main\n\nimport "fmt"\n\nfunc main() {\n ch := make(chan int, 3) // canal com buffer para 3 elementos\n ch <- 1\n ch <- 2\n ch <- 3\n // ch <- 4 // bloqueio, pois o buffer está cheio\n fmt.Println(<-ch)\n fmt.Println(<-ch)\n fmt.Println(<-ch)\n}\n\n3.2. Canais Unidirecionais
\nPara segurança de tipos e indicação explícita da função do canal, são usados canais unidirecionais: chan<- (apenas envio) e <-chan (apenas recebimento).
package main\n\nimport "fmt"\n\nfunc sender(out chan<- int) {\n for i := 0; i < 5; i++ {\n out <- i\n }\n close(out)\n}\n\nfunc receiver(in <-chan int) {\n for val := range in {\n fmt.Println(val)\n }\n}\n\nfunc main() {\n ch := make(chan int)\n go sender(ch)\n receiver(ch)\n}\n\n3.3. Operador Select
\nselect permite que uma goroutine aguarde vários canais simultaneamente. Isto