Aula 17 - Concorrência Extrema com Goroutines e Channels 🐹
Objetivo Pedagógico
Objetivo: Domínio do modelo de concorrência Communicating Sequential Processes (CSP) em Go: Goroutines leves, canais unbuffered/buffered, multiplexação com select e sync.WaitGroup.
📑 1. Fundamentos Teóricos & Análise Técnica
O principal diferencial da linguagem Go reside em seu modelo nativo de concorrência baseado no formalismo matemático CSP (Communicating Sequential Processes) de Tony Hoare. O mantra canônico de Go sintetiza essa filosofia: "Não se comunique compartilhando memória; em vez disso, compartilhe memória comunicando-se".
Componentes do motor de execução concorrente de Go: 1. Goroutines: Threads leves gerenciadas pelo runtime de Go em espaço de usuário (não pelo kernel do sistema operacional). Enquanto uma thread do SO aloca tipicamente 1MB a 8MB de pilha (stack), uma goroutine inicia com apenas 2KB de pilha, que cresce e encolhe dinamicamente na heap conforme a demanda. Isso permite que um único processo hospede centenas de milhares de goroutines simultâneas. 2. Canais (Channels): Tubos tipados através dos quais dados são transmitidos entre goroutines de forma sincronizada e thread-safe sem travas explícitas (mutexes). 3. Escalonador Go M:N (GMP Scheduler): Multiplexa M goroutines em N threads do SO distribuídas sobre P processadores lógicos, implementando roubo de trabalho (work-stealing) para balanceamento de carga de CPU.
📐 Arquitetura Conceitual & Diagrama de Fluxo
graph TD
subgraph GMPScheduler ["Go GMP Scheduler Runtime"]
P1["Processador Lógico P1"] --> G1["Goroutine G1"]
P1 --> G2["Goroutine G2"]
P2["Processador Lógico P2"] --> G3["Goroutine G3"]
end
G1 -->|Canal Tipado (ch <- dado)| Ch["Channel Sincronizado"]
Ch -->|Leitura (dado := <-ch)| G3
style GMPScheduler fill:#e3f2fd,stroke:#1565c0
style Ch fill:#fff3e0,stroke:#e65100
style G1 fill:#e8f5e9,stroke:#2e7d32
style G3 fill:#e8f5e9,stroke:#2e7d32 🔍 Pilares e Diretrizes Técnicas
Nesta unidade, aprofundamos os seguintes conceitos fundamentais: - Pilha Dinâmica de 2KB: Consumo minúsculo de memória viabilizando concorrência massiva. - Multiplexação com select: Capacidade de aguardar a resolução do primeiro canal pronto de forma não-bloqueante. - Prevenção de Goroutine Leaks: Uso obrigatório de context.Context para cancelar goroutines quando a conexão do cliente é interrompida. - Sincronização com sync.WaitGroup: Coordenação determinística da conclusão de tarefas em paralelo.
🛠️ 2. Implementação Prática em Golang Runtime e Concorrência CSP
Abaixo está a implementação técnica de referência, estruturada com padrões de engenharia de software e foco em robustez:
// worker_pool.go (Padrão Worker Pool com Canais e WaitGroup)
package main
import (
"fmt"
"sync"
"time"
)
// Função que executa o trabalho em background
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for j := range jobs {
fmt.Printf("[Worker %d] Processando job %d\n", id, j)
time.Sleep(100 * time.Millisecond) // Simula computação
results <- j * 2
}
}
func main() {
const numJobs = 10
const numWorkers = 3
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
// Inicializa 3 workers concorrentes
for w := 1; w <= numWorkers; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
// Envia jobs para o canal
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs) // Fecha para sinalizar aos workers o fim da fila
wg.Wait()
close(results)
for res := range results {
fmt.Println("Resultado coletado:", res)
}
}
💡 Análise Passo a Passo do Código
- Canais com Buffer:
make(chan int, numJobs)evita bloqueio desnecessário dos produtores antes do esgotamento da capacidade. - Coordenação com WaitGroup:
wg.Add(1)edefer wg.Done()garantem que a funçãomainaguarde a finalização de todos os workers. - Fechamento com close():
close(jobs)notifica a cláusularangedentro de cada worker de que não haverá novos dados.
🎯 3. Próximos Passos & Sequência Didática
-
Slides da Aula
-
Quiz de Fixação
-
Exercícios Práticos
-
Desafio de Projeto