Concorrência e Paralelismo para Iniciantes: Back-end (Parte 4) – Guia Completo

Concorrência e Paralelismo para Iniciantes: Back-end (Parte 4) – Guia Completo






Concorrência e Paralelismo: Parte 2 — Entendendo Back-end para Iniciantes (Parte 4)


1. Fundamentos: Concorrência vs Paralelismo

A distinção entre concorrência e paralelismo é central para tomadas de decisão em backend. Enquanto concorrência descreve a capacidade de um sistema gerenciar várias tarefas de forma intercalada, paralelismo refere-se à execução efetiva de múltiplas tarefas simultaneamente, tipicamente aproveitando múltiplas unidades de hardware.

  • Concorrência: modela a coordenação de várias tarefas que progridem, mesmo que apenas uma CPU esteja ativa em um dado instante.
  • Paralelismo: ocorre quando várias CPUs/ núcleos executam etapas de diferentes tarefas ao mesmo tempo.
  • Tipos de carga: I/O-bound (rede, disco, banco de dados) tende a beneficiar de concorrência eficiente; CPU-bound (processamento) se beneficia de paralelismo verdadeiro.
  • Consecções práticas: projetar para evitar bloqueios, contenção e latência desnecessária, mantendo a escalabilidade sob controle.

2. Modelos de Execução

Os modelos de execução descrevem como as tarefas são distribuídas e executadas. Os dois grandes eixos são: threads/processos e o modelo orientado a eventos (event loop).

  • Threads e processos: paralelismo real quando há múltiplos núcleos; pools de threads ajudam a gerenciar o custo de criação/destruição.
  • Event loop e I/O não bloqueante: frameworks como Node.js e ambientes assíncronos permitem avançar com outras tarefas enquanto aguardam operações de I/O.
  • Comunicação entre unidades: canais, filas de tarefas e estruturas de sincronização (mutex, semáforos) são pilares para coordenação segura.
  • Trade-offs: threads trazem overhead; loops de eventos reduzem overhead de thread, mas aumentam complexidade de fluxo.

3. Padrões de Arquitetura para Concorrência

  • Pool de workers: reuso de threads para executar tarefas em paralelo com overhead controlado.
  • Fila de tarefas: desacopla produtores e consumidores, gerencia picos de carga e evita sobrecarga súbita.
  • Sincronização e comunicação: mutexes, semáforos e canais para proteger dados compartilhados e coordenar etapas.
  • Evitar deadlocks e starvation: adquirir recursos na mesma ordem, tempo limite de espera e desenho sem dependências cíclicas.
  • Observabilidade básica: métricas de throughput, latência e tempo de resposta ajudam a detectar gargalos de concorrência.

4. Observabilidade e Performance

  • Throughput e latência: medem quantas operações por unidade de tempo e o tempo médio de conclusão, respectivamente.
  • Latência de cauda: P95, P99 ajudam a entender experiências de usuários em cenários de pico.
  • Dimensões de escalonamento: CPU-bound vs I/O-bound; mudanças no paralelismo impactam de forma diferente cada perfil.
  • Notas sobre Python e GIL: ambientes com GIL podem limitar paralelismo real em CPU-bound; em I/O-bound, concorrência ainda traz ganhos perceptíveis.

Exemplo prático: concorrência com goroutines (Go)

O exemplo a seguir ilustra um modelo simples de worker pool usando goroutines e canais para processar tarefas de forma concorrente. Propõe uma forma clara de entender a distribuição de trabalho e a coleta de resultados sem bloquear o fluxo principal.

package main

import (
  "fmt"
  "time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
  for j := range jobs {
    // simula algum processamento
    time.Sleep(time.Duration(100+id*50) * time.Millisecond)
    results <- j * 2
  }
}

func main() {
  const workers = 4
  jobs := make(chan int, 8)
  results := make(chan int, 8)

  for i := 1; i <= workers; i++ {
    go worker(i, jobs, results)
  }

  for j := 1; j <= 8; j++ {
    jobs <- j
  }
  close(jobs)

  for i := 0; i < 8; i++ {
    fmt.Println("result:", <-results)
  }
}

Leia também

Gostou do conteúdo? Acesse outros posts para ampliar seu entendimento sobre back-end, concorrência e desempenho.