Goroutines & scheduler M:Nleves
Goroutine começa com ~2KB de stack que cresce sob demanda; thread do SO custa ~1MB. O scheduler do runtime multiplexa milhares de goroutines em poucas threads (M:N), então 1000 goroutines rodam sobre o nº de cores da máquina.
go worker(id) // agenda, não cria thread
Concorrência vs paralelismo2 coisas diferentes
Concorrência é estruturar o programa em tarefas independentes; paralelismo é executar várias ao mesmo tempo. Código concorrente roda em 1 core (intercalado); vira paralelo quando há cores sobrando. Um habilita o outro, não são sinônimos.
Channels: unbuffered vs bufferedrendezvous
Unbuffered é rendezvous: send bloqueia até alguém receber (sincroniza as duas pontas). Buffered (cap N) só bloqueia quando o buffer enche; desacopla produtor de consumidor até o limite.
ch := make(chan int) // rendezvous
ch := make(chan int, 10) // buffer 10
CSP: comunicar > compartilharfilosofia
"Don't communicate by sharing memory; share memory by communicating." Em vez de várias goroutines mexendo na mesma variável com lock, passe o dado por um channel: uma goroutine dona do estado por vez, sem race.
Mutex vs channelsescolha certa
Mutex protege estado compartilhado (contador, cache, map) · guarda curta, lock/unlock. Channel coordena transferência de dado e fluxo entre goroutines. Errado: mutex pra orquestrar pipeline, channel pra incrementar contador.
var mu sync.Mutex; mu.Lock(); defer mu.Unlock()
RWMutex: muitos leitoresread-heavy
RLock deixa N leitores entrarem juntos; Lock (escrita) é exclusivo e barra todos. Vale quando leitura domina e escrita é rara (config, cache). Em write-heavy o overhead supera o ganho · use Mutex simples.
mu.RLock() // vários leem · mu.Lock() // 1 escreve
Race condition-race
Dois acessos concorrentes à mesma memória, com pelo menos um write, sem sincronização. O clássico é read-modify-write (x++): ler, somar, gravar não é atômico; updates se perdem. Rode go test -race / go run -race pra detectar.
// x++ NÃO é atômico → corrida
go run -race main.go
Deadlock: 4 condiçõesCoffman
Trava só com as 4 de Coffman juntas: exclusão mútua, hold-and-wait, sem preempção e espera circular. Quebre uma e some o deadlock. Na prática: lock ordering (sempre travar A antes de B) mata a espera circular.
// sempre: Lock(a) → Lock(b), nunca invertido
Context: cancel cooperativoctx.Done()
Cancelamento e timeout são cooperativos: o context sinaliza via ctx.Done(), mas a goroutine precisa checar e sair sozinha. Propague o ctx pela cadeia de chamadas; cancelar o pai cancela todos os filhos.
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
Goroutine leaksilencioso
Goroutine bloqueada pra sempre (channel sem receiver, sem caminho de saída) nunca é coletada · vaza memória e cresce sem aparecer no log. Sempre dê rota de saída: select com ctx.Done(), buffer adequado ou fechar o channel.
select { case <-ctx.Done(): return · case v := <-ch: ... }