A concorrência é uma das features mais poderosas e distintivas de Go. Enquanto outras linguagens tornam a programação paralela complexa e cheia de boilerplate, Go a torna simples e elegante com goroutines e channels.
Neste quarto artigo da série “Golang para Iniciantes”, vamos desvendar os segredos da concorrência em Go. Você aprenderá a executar múltiplas tarefas simultaneamente de forma segura e eficiente.
Se você está chegando agora:
📖 ← Artigo Anterior: Controle de Fluxo em Go
Por Que Concorrência Importa?
O Problema: Execução Sequencial
Imagine buscar dados de 5 APIs diferentes:
// Execução sequencial: lenta!
result1 := buscarAPI1() // 2 segundos
result2 := buscarAPI2() // 2 segundos
result3 := buscarAPI3() // 2 segundos
result4 := buscarAPI4() // 2 segundos
result5 := buscarAPI5() // 2 segundos
// Total: 10 segundos
A Solução: Execução Concorrente
// Execução concorrente: rápida!
go buscarAPI1() // Inicia e continua
igo buscarAPI2() // Inicia e continua
go buscarAPI3() // Inicia e continua
go buscarAPI4() // Inicia e continua
go buscarAPI5() // Inicia e continua
// Todas executam em paralelo!
Goroutines
Goroutines são funções ou métodos que executam concorrentemente com outras goroutines. São como threads, mas muito mais leves.
Criando Goroutines
Use a palavra-chave go:
package main
import (
"fmt"
"time"
)
func dizerOla() {
for i := 0; i < 3; i++ {
fmt.Println("Olá!")
time.Sleep(100 * time.Millisecond)
}
}
func dizerMundo() {
for i := 0; i < 3; i++ {
fmt.Println("Mundo!")
time.Sleep(100 * time.Millisecond)
}
}
func main() {
go dizerOla() // Inicia goroutine
go dizerMundo() // Inicia outra goroutine
// Aguarda um pouco para as goroutines terminarem
time.Sleep(1 * time.Second)
fmt.Println("Fim")
}
Output (a ordem pode variar):
Olá!
Mundo!
Olá!
Mundo!
Olá!
Mundo!
Fim
Goroutines com Funções Anônimas
package main
import (
"fmt"
"time"
)
func main() {
// Goroutine com função anônima
go func() {
for i := 0; i < 3; i++ {
fmt.Println("Goroutine:", i)
time.Sleep(50 * time.Millisecond)
}
}()
// Código principal continua executando
for i := 0; i < 3; i++ {
fmt.Println("Main:", i)
time.Sleep(50 * time.Millisecond)
}
time.Sleep(200 * time.Millisecond)
}
Passando Parâmetros
package main
import (
"fmt"
"time"
)
func processar(id int, dados string) {
fmt.Printf("Goroutine %d: processando %s\n", id, dados)
time.Sleep(100 * time.Millisecond)
fmt.Printf("Goroutine %d: concluída\n", id)
}
func main() {
for i := 1; i <= 5; i++ {
go processar(i, fmt.Sprintf("tarefa-%d", i))
}
time.Sleep(1 * time.Second)
fmt.Println("Todas as goroutines concluídas")
}
⚠️ Importante: Goroutines são leves (iniciam com ~2KB de stack), mas não são gratuitas. Criar milhões pode exaurir memória.
Channels (Canais)
Channels são a forma de comunicação entre goroutines. Eles permitem que goroutines troquem dados de forma segura.
Criando Channels
package main
func main() {
// Canal de inteiros sem buffer
ch := make(chan int)
// Canal de strings com buffer
chBuffered := make(chan string, 10)
_ = ch
_ = chBuffered
}
Enviando e Recebendo
package main
import "fmt"
func main() {
mensagens := make(chan string)
// Goroutine que envia dados
go func() {
mensagens <- "Olá do canal!"
}()
// Recebe dados do canal
msg := <-mensagens
fmt.Println(msg)
}
Nota: A operação de envio (
<-) bloqueia até que alguém receba, e a operação de receber (<-) bloqueia até que alguém envie.
Channels com Buffer
Channels bufferizados não bloqueiam imediatamente:
package main
import "fmt"
func main() {
// Canal com buffer de 2
mensagens := make(chan string, 2)
// Envia sem bloquear (ainda há espaço)
mensagens <- "primeira"
mensagens <- "segunda"
// Recebe
fmt.Println(<-mensagens)
fmt.Println(<-mensagens)
}
Padrão Worker Pool
Distribui trabalho entre múltiplas goroutines:
package main
import (
"fmt"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("Worker %d: processando job %d\n", id, j)
time.Sleep(time.Second) // Simula trabalho
results <- j * 2
}
}
func main() {
const numJobs = 10
const numWorkers = 3
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
// Inicia workers
for w := 1; w <= numWorkers; w++ {
go worker(w, jobs, results)
}
// Envia jobs
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// Coleta resultados
for a := 1; a <= numJobs; a++ {
resultado := <-results
fmt.Printf("Resultado: %d\n", resultado)
}
}
Sincronização com sync
WaitGroup
Espere que múltiplas goroutines terminem:
package main
import (
"fmt"
"sync"
"time"
)
func processar(id int, wg *sync.WaitGroup) {
defer wg.Done() // Decrementa o contador quando terminar
fmt.Printf("Iniciando tarefa %d\n", id)
time.Sleep(time.Second)
fmt.Printf("Tarefa %d concluída\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1) // Incrementa o contador
go processar(i, &wg)
}
wg.Wait() // Bloqueia até o contador zerar
fmt.Println("Todas as tarefas concluídas")
}
Mutex
Protege acesso concorrente a dados compartilhados:
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() {
var wg sync.WaitGroup
contador := Contador{}
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
contador.Incrementar()
}()
}
wg.Wait()
fmt.Printf("Contador: %d\n", contador.Valor())
}
RWMutex
Permite múltiplos leitores ou um único escritor:
package main
import (
"fmt"
"sync"
"time"
)
type Cache struct {
mu sync.RWMutex
dados map[string]string
}
func (c *Cache) Get(chave string) (string, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
valor, ok := c.dados[chave]
return valor, ok
}
func (c *Cache) Set(chave, valor string) {
c.mu.Lock()
defer c.mu.Unlock()
c.dados[chave] = valor
}
func main() {
cache := Cache{dados: make(map[string]string)}
// Escritor
go func() {
for i := 0; i < 10; i++ {
cache.Set(fmt.Sprintf("key%d", i), fmt.Sprintf("value%d", i))
time.Sleep(100 * time.Millisecond)
}
}()
// Múltiplos leitores
for i := 0; i < 3; i++ {
go func(id int) {
for j := 0; j < 10; j++ {
if val, ok := cache.Get(fmt.Sprintf("key%d", j)); ok {
fmt.Printf("Leitor %d: %s\n", id, val)
}
time.Sleep(50 * time.Millisecond)
}
}(i)
}
time.Sleep(2 * time.Second)
}
Select
O select permite esperar por múltiplas operações de canal simultaneamente.
Select Básico
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "canal 1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "canal 2"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println("Recebido:", msg1)
case msg2 := <-ch2:
fmt.Println("Recebido:", msg2)
}
}
}
Select com Timeout
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
go func() {
time.Sleep(2 * time.Second)
ch <- "resultado"
}()
select {
case res := <-ch:
fmt.Println("Recebido:", res)
case <-time.After(1 * time.Second):
fmt.Println("Timeout!")
}
}
Select Não-Bloqueante
package main
import "fmt"
func main() {
mensagens := make(chan string)
sinais := make(chan bool)
// Envio não-bloqueante
select {
case mensagens <- "olá":
fmt.Println("Mensagem enviada")
default:
fmt.Println("Canal cheio, mensagem não enviada")
}
// Recebimento não-bloqueante
select {
case msg := <-mensagens:
fmt.Println("Recebido:", msg)
default:
fmt.Println("Nenhuma mensagem disponível")
}
_ = sinais
}
Padrões de Concorrência
1. Generator (Gerador)
package main
import "fmt"
func geradorNumeros() <-chan int {
ch := make(chan int)
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}()
return ch
}
func main() {
for num := range geradorNumeros() {
fmt.Println(num)
}
}
2. Pipeline
package main
import "fmt"
// Etapa 1: Gera números
func gerar(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
// Etapa 2: Quadrado
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() {
// Pipeline: gerar -> quadrado
for resultado := range quadrado(gerar(2, 3, 4, 5)) {
fmt.Println(resultado) // 4, 9, 16, 25
}
}
3. Fan-Out / Fan-In
package main
import (
"fmt"
"sync"
)
func producer(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func worker(id int, in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n // Processa
}
close(out)
}()
return out
}
func merge(chs ...<-chan int) <-chan int {
var wg sync.WaitGroup
out := make(chan int)
output := func(ch <-chan int) {
for n := range ch {
out <- n
}
wg.Done()
}
wg.Add(len(chs))
for _, ch := range chs {
go output(ch)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
func main() {
in := producer(1, 2, 3, 4, 5, 6, 7, 8)
// Fan-out: múltiplos workers
c1 := worker(1, in)
c2 := worker(2, in)
c3 := worker(3, in)
// Fan-in: merge dos resultados
for n := range merge(c1, c2, c3) {
fmt.Println(n)
}
}
Exercícios Práticos
Exercício 1: Download Concorrente
Implemente um downloader que baixa múltiplas URLs concorrentemente.
Ver solução
package main
import (
"fmt"
"time"
)
func download(url string, resultado chan<- string) {
// Simula download
time.Sleep(time.Duration(len(url)) * 100 * time.Millisecond)
resultado <- fmt.Sprintf("Downloaded: %s", url)
}
func main() {
urls := []string{
"https://api1.com",
"https://api2.com",
"https://api3.com",
}
resultados := make(chan string, len(urls))
for _, url := range urls {
go download(url, resultados)
}
for i := 0; i < len(urls); i++ {
fmt.Println(<-resultados)
}
}
Exercício 2: Soma Concorrente
Calcule a soma de um slice dividindo o trabalho entre goroutines.
Ver solução
package main
import (
"fmt"
"sync"
)
func somaParcial(nums []int, resultado chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
soma := 0
for _, n := range nums {
soma += n
}
resultado <- soma
}
func main() {
numeros := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
numWorkers := 2
tamanhoParte := len(numeros) / numWorkers
resultados := make(chan int, numWorkers)
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
inicio := i * tamanhoParte
fim := inicio + tamanhoParte
if i == numWorkers-1 {
fim = len(numeros)
}
wg.Add(1)
go somaParcial(numeros[inicio:fim], resultados, &wg)
}
go func() {
wg.Wait()
close(resultados)
}()
total := 0
for parcial := range resultados {
total += parcial
}
fmt.Printf("Soma total: %d\n", total)
}
Dicas e Melhores Práticas
1. Não Use Concorrência Desnecessariamente
// Ruim: overhead de goroutine para tarefa simples
go fmt.Println("Olá")
// Bom: execução sequencial é mais simples
fmt.Println("Olá")
2. Sempre Trate Race Conditions
Use go run -race para detectar condições de corrida:
go run -race meu-programa.go
3. Feche Channels Apenas Quando Apropriado
// Quem envia fecha o canal
func producer(ch chan<- int) {
defer close(ch)
for i := 0; i < 10; i++ {
ch <- i
}
}
4. Use Context para Cancelamento
package main
import (
"context"
"fmt"
"time"
)
func processar(ctx context.Context, id int) {
select {
case <-ctx.Done():
fmt.Printf("Worker %d cancelado\n", id)
return
case <-time.After(time.Second):
fmt.Printf("Worker %d concluído\n", id)
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
for i := 1; i <= 5; i++ {
go processar(ctx, i)
}
time.Sleep(3 * time.Second)
}
Próximos Passos
Parabéns! Você domina os fundamentos da concorrência em Go. Prepare-se para aplicar esse conhecimento:
Próximo Artigo da Série
📖 Criando sua Primeira API REST em Go → — Construa uma API web completa usando tudo o que aprendeu.
Recursos Recomendados
Desafios
- Rate Limiter: Implemente um limitador de requisições usando channels
- Crawler Web: Crie um crawler concorrente com limite de profundidade
- Chat Simples: Servidor de chat usando channels
- Processador de Imagens: Processe imagens em paralelo
Artigo anterior: Controle de Fluxo em Go ←
Próximo artigo: Criando sua Primeira API REST em Go →
Última atualização: 09 de fevereiro de 2026
Versão do Go: 1.23