Pular para conteúdo

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

  1. Canais com Buffer: make(chan int, numJobs) evita bloqueio desnecessário dos produtores antes do esgotamento da capacidade.
  2. Coordenação com WaitGroup: wg.Add(1) e defer wg.Done() garantem que a função main aguarde a finalização de todos os workers.
  3. Fechamento com close(): close(jobs) notifica a cláusula range dentro de cada worker de que não haverá novos dados.

🎯 3. Próximos Passos & Sequência Didática