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çãoSintaxe
Criar goroutinego func()
Goroutine anônimago func() { ... }()
WaitGroup.Addwg.Add(1) antes de go
WaitGroup.Donedefer wg.Done() dentro da goroutine
WaitGroup.Waitwg.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

TipoSintaxeComportamento
Sem buffermake(chan int)Bloqueia até sender e receiver estejam prontos
Com buffermake(chan int, 10)Bloqueia apenas quando o buffer está cheio/vazio
Somente enviochan<- intSó pode enviar
Somente recepção<-chan intSó 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ãoUso
select com time.AfterTimeout em operações
select com defaultOperação não-bloqueante
select com ctx.Done()Cancelamento via context
select em loop forListener 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
}
TipoDescriçãoQuando usar
sync.MutexExclusão mútuaLeitura e escrita frequentes
sync.RWMutexLeitura concorrente, escrita exclusivaMuitas 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()
}
TipoUso principal
sync.OnceInicialização lazy (singletons, config)
sync.PoolCache de objetos temporários (buffers, conexões)
sync.MapMap 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)
	}
}
PadrãoDescriçãoLink
Fan-Out/Fan-InDistribui trabalho e agrega resultadosPadrões de concorrência
PipelineEstágios conectados por channelsConcorrência avançada
Worker PoolPool de goroutines com channel de jobsConcorrência em Go
Context cancelamentoPropagar cancelamento/timeoutContext no glossário
SemaphoreLimitar concorrência com buffered channelTestes de concorrência

Detectar Problemas

FerramentaComandoDetecta
Race detectorgo test -race ./...Data races
DeadlockRuntime panic automáticoDeadlocks
Goroutine leakruntime.NumGoroutine()Goroutines que nunca terminam
pprofgo tool pprofPerfil 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