Concorrência em Go — Referência Rápida
Go foi projetada com concorrência como recurso de primeira classe. Esta referência cobre goroutines, channels, o pacote sync e padrões comuns. Para um tutorial aprofundado, veja Concorrência em Go e Padrões de Concorrência.
Goroutines
Uma goroutine é uma thread leve gerenciada pelo runtime do Go (~2 KB de stack inicial). Veja o glossário de goroutines para mais contexto.
package main
import (
"fmt"
"sync"
)
func trabalho(id int) {
fmt.Printf("goroutine %d executando\n", id)
}
func main() {
// Iniciar goroutine com função nomeada
go trabalho(1)
// Goroutine anônima
go func(msg string) {
fmt.Println(msg)
}("olá da goroutine")
// Esperar com WaitGroup
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
fmt.Printf("worker %d\n", n)
}(i)
}
wg.Wait()
}
| Operação | Sintaxe |
|---|
| Criar goroutine | go func() |
| Goroutine anônima | go func() { ... }() |
| WaitGroup.Add | wg.Add(1) antes de go |
| WaitGroup.Done | defer wg.Done() dentro da goroutine |
| WaitGroup.Wait | wg.Wait() bloqueia até counter = 0 |
Channels
Channels são a principal forma de comunicação entre goroutines. Consulte channels no glossário para a teoria.
Tipos de Channels
| Tipo | Sintaxe | Comportamento |
|---|
| Sem buffer | make(chan int) | Bloqueia até sender e receiver estejam prontos |
| Com buffer | make(chan int, 10) | Bloqueia apenas quando o buffer está cheio/vazio |
| Somente envio | chan<- int | Só pode enviar |
| Somente recepção | <-chan int | Só pode receber |
Operações Básicas
package main
import "fmt"
func main() {
// Channel sem buffer
ch := make(chan string)
go func() {
ch <- "mensagem" // envia
}()
msg := <-ch // recebe (bloqueia até ter valor)
fmt.Println(msg)
// Channel com buffer
buf := make(chan int, 3)
buf <- 1
buf <- 2
buf <- 3
// buf <- 4 // bloquearia — buffer cheio
fmt.Println(<-buf) // 1
fmt.Println(<-buf) // 2
}
Fechar e Iterar sobre Channels
package main
import "fmt"
func gerador(n int) <-chan int {
ch := make(chan int)
go func() {
for i := 0; i < n; i++ {
ch <- i
}
close(ch) // sinaliza que não há mais valores
}()
return ch
}
func main() {
for v := range gerador(5) {
fmt.Println(v)
}
// Verificar se channel foi fechado
ch := make(chan int, 1)
ch <- 42
close(ch)
v, ok := <-ch
fmt.Println(v, ok) // 42 true
v, ok = <-ch
fmt.Println(v, ok) // 0 false
}
Select
select permite esperar em múltiplos channels simultaneamente. Veja select no glossário.
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(100 * time.Millisecond)
ch1 <- "canal 1"
}()
go func() {
time.Sleep(200 * time.Millisecond)
ch2 <- "canal 2"
}()
// Espera o primeiro que estiver pronto
select {
case msg := <-ch1:
fmt.Println(msg)
case msg := <-ch2:
fmt.Println(msg)
case <-time.After(1 * time.Second):
fmt.Println("timeout")
}
// Select com default (não-bloqueante)
select {
case msg := <-ch1:
fmt.Println(msg)
default:
fmt.Println("nenhum canal pronto")
}
}
| Padrão | Uso |
|---|
select com time.After | Timeout em operações |
select com default | Operação não-bloqueante |
select com ctx.Done() | Cancelamento via context |
select em loop for | Listener contínuo |
Pacote sync
Mutex
Use Mutex para proteger dados compartilhados contra race conditions.
package main
import (
"fmt"
"sync"
)
type Contador struct {
mu sync.Mutex
valor int
}
func (c *Contador) Incrementar() {
c.mu.Lock()
defer c.mu.Unlock()
c.valor++
}
func (c *Contador) Valor() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.valor
}
func main() {
c := &Contador{}
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
c.Incrementar()
}()
}
wg.Wait()
fmt.Println(c.Valor()) // 1000
}
RWMutex
package main
import "sync"
type Cache struct {
mu sync.RWMutex
data map[string]string
}
func (c *Cache) Get(key string) (string, bool) {
c.mu.RLock() // múltiplos leitores simultâneos
defer c.mu.RUnlock()
v, ok := c.data[key]
return v, ok
}
func (c *Cache) Set(key, value string) {
c.mu.Lock() // exclusivo para escrita
defer c.mu.Unlock()
c.data[key] = value
}
| Tipo | Descrição | Quando usar |
|---|
sync.Mutex | Exclusão mútua | Leitura e escrita frequentes |
sync.RWMutex | Leitura concorrente, escrita exclusiva | Muitas leituras, poucas escritas |
sync.Once, sync.Pool e sync.Map
package main
import (
"fmt"
"sync"
)
// Once — executa uma função uma única vez
var once sync.Once
func inicializar() {
once.Do(func() {
fmt.Println("inicializado (só executa uma vez)")
})
}
// Pool — reutilização de objetos temporários
var bufPool = sync.Pool{
New: func() any {
return make([]byte, 0, 1024)
},
}
func usarPool() {
buf := bufPool.Get().([]byte)
defer bufPool.Put(buf[:0])
// usar buf...
}
// Map — map thread-safe (para chaves estáveis)
var cache sync.Map
func usarMap() {
cache.Store("chave", "valor")
v, ok := cache.Load("chave")
fmt.Println(v, ok)
cache.Delete("chave")
}
func main() {
inicializar()
inicializar() // não executa de novo
usarPool()
usarMap()
}
| Tipo | Uso principal |
|---|
sync.Once | Inicialização lazy (singletons, config) |
sync.Pool | Cache de objetos temporários (buffers, conexões) |
sync.Map | Map concorrente — use para chaves que raramente mudam |
Padrões Comuns de Concorrência
Fan-Out / Fan-In
package main
import (
"fmt"
"sync"
)
func fanOut(input <-chan int, workers int) []<-chan int {
channels := make([]<-chan int, workers)
for i := 0; i < workers; i++ {
ch := make(chan int)
channels[i] = ch
go func(in <-chan int, out chan<- int) {
for v := range in {
out <- v * 2 // processa
}
close(out)
}(input, ch)
}
return channels
}
func fanIn(channels ...<-chan int) <-chan int {
out := make(chan int)
var wg sync.WaitGroup
for _, ch := range channels {
wg.Add(1)
go func(c <-chan int) {
defer wg.Done()
for v := range c {
out <- v
}
}(ch)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
func main() {
input := make(chan int, 5)
for i := 1; i <= 5; i++ {
input <- i
}
close(input)
workers := fanOut(input, 3)
for result := range fanIn(workers...) {
fmt.Println(result)
}
}
Pipeline
package main
import "fmt"
func gerar(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func quadrado(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
func main() {
for v := range quadrado(gerar(2, 3, 4)) {
fmt.Println(v)
}
}
Detectar Problemas
| Ferramenta | Comando | Detecta |
|---|
| Race detector | go test -race ./... | Data races |
| Deadlock | Runtime panic automático | Deadlocks |
| Goroutine leak | runtime.NumGoroutine() | Goroutines que nunca terminam |
| pprof | go tool pprof | Perfil de goroutines em execução |
Veja o cheatsheet de testes e debug para comandos detalhados de profiling. Para comparar modelos de concorrência, veja como Rust usa async/await com Tokio e como Zig implementa concorrência com async frames sem alocação no heap.
Veja Também