Concorrência e Multithreading em Go: Nível Avançado

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Concorrência e Multithreading em Go: Nível Avançado

\n\n

Concorrê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\n

1. Goroutines: Gerenciamento do Ciclo de Vida e Prioridades

\n\n

Goroutines 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.

\n\n

Inicialização básica de uma goroutine:

\n
package 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\n

Problema: 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.

\n\n

2. Sincronização com sync.WaitGroup

\n\n

sync.WaitGroup permite aguardar a conclusão de um número determinado de goroutines. É uma ferramenta básica, mas poderosa, para sincronização.

\n\n
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\n

Importante: 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.

\n\n

3. Canais: Bufferização, Direção e Select

\n\n

Canais 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.

\n\n

3.1. Canais com Buffer

\n

Canais com buffer permitem enviar dados sem recebimento imediato. Isso reduz bloqueios e aumenta a taxa de transferência.

\n
package 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\n

3.2. Canais Unidirecionais

\n

Para 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).

\n
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\n

3.3. Operador Select

\n

select permite que uma goroutine aguarde vários canais simultaneamente. Isto

Blogs

Book Recommendations