# Como Processar Consultas de CPF em Paralelo Usando Goroutines

> Aprenda a processar consultas de CPF em paralelo usando goroutines em Go, com channels, semáforos, errgroup e controle de concorrência.

**Publicado:** 29/08/2024
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/processar-consultas-cpf-paralelo-goroutines

---


Para processar consultas de CPF em paralelo com goroutines em Go, utilize `sync.WaitGroup` para lotes simples, um channel como semáforo para controlar a concorrência máxima e `errgroup` quando precisar de propagação de erros e cancelamento coordenado. Cada goroutine consome apenas alguns kilobytes de memória, permitindo processar centenas de CPFs simultaneamente com latência próxima à de uma única consulta sequencial.

## Introdução

Go foi projetada desde o início para concorrência, e as goroutines são a primitiva mais poderosa da linguagem para esse propósito. Processar centenas ou milhares de consultas de CPF em paralelo usando goroutines é extremamente eficiente: cada goroutine consome apenas alguns kilobytes de memória, e o scheduler do Go distribui o trabalho automaticamente entre os cores disponíveis.

## Goroutines vs. threads tradicionais

Goroutines oferecem vantagens significativas sobre threads do sistema operacional.

| Característica | Goroutine | Thread do SO |
|---|---|---|
| Memória inicial | ~2KB | ~1MB |
| Criação | Microssegundos | Milissegundos |
| Troca de contexto | ~200ns | ~1000ns |
| Limite prático | Milhões | Milhares |
| Gerenciamento | Runtime do Go | Sistema operacional |
| Comunicação | Channels (seguro) | Mutexes (propenso a erros) |

**Goroutines são leves** -- é possível criar milhares sem impacto significativo na memória.

**Channels são seguros** -- permitem comunicação entre goroutines sem necessidade de locks manuais.

**Scheduler eficiente** -- o runtime do Go multiplexa goroutines em threads do SO automaticamente.

---

## Padrão básico com WaitGroup

A abordagem mais simples usa `sync.WaitGroup` para aguardar todas as goroutines completarem.

```go
package main

import (
 "encoding/json"
 "fmt"
 "net/http"
 "os"
 "sync"
 "time"
)

type ResultadoCPF struct {
 CPF string `json:"cpf"`
 Sucesso bool `json:"sucesso"`
 Nome string `json:"nome,omitempty"`
 Erro string `json:"erro,omitempty"`
}

type APIResponse struct {
 Success bool `json:"success"`
 Data struct {
 Name string `json:"name"`
 } `json:"data"`
}

func consultarCPF(cpf string, apiKey string) ResultadoCPF {
 url := fmt.Sprintf("https://api.cpfhub.io/cpf/%s", cpf)
 req, _ := http.NewRequest("GET", url, nil)
 req.Header.Set("x-api-key", apiKey)

 client := &http.Client{Timeout: 10 * time.Second}
 resp, err := client.Do(req)
 if err != nil {
 return ResultadoCPF{CPF: cpf, Sucesso: false, Erro: err.Error()}
 }
 defer resp.Body.Close()

 var resultado APIResponse
 json.NewDecoder(resp.Body).Decode(&resultado)

 if resultado.Success {
 return ResultadoCPF{CPF: cpf, Sucesso: true, Nome: resultado.Data.Name}
 }
 return ResultadoCPF{CPF: cpf, Sucesso: false, Erro: "nao encontrado"}
}

func consultarLoteSimples(cpfs []string, apiKey string) []ResultadoCPF {
 var wg sync.WaitGroup
 resultados := make([]ResultadoCPF, len(cpfs))

 for i, cpf := range cpfs {
 wg.Add(1)
 go func(idx int, c string) {
 defer wg.Done()
 resultados[idx] = consultarCPF(c, apiKey)
 }(i, cpf)
 }

 wg.Wait()
 return resultados
}

func main() {
 apiKey := os.Getenv("CPFHUB_API_KEY")
 cpfs := []string{"12345678901", "98765432100", "11122233344"}

 inicio := time.Now()
 resultados := consultarLoteSimples(cpfs, apiKey)
 duracao := time.Since(inicio)

 for _, r := range resultados {
 fmt.Printf("CPF: %s | Sucesso: %v | Nome: %s\n", r.CPF, r.Sucesso, r.Nome)
 }
 fmt.Printf("Duracao: %v\n", duracao)
}
```

---

## Controle de concorrência com semáforo

Para evitar sobrecarregar a API, use um channel como semáforo para limitar goroutines simultâneas.

```go
func consultarLoteControlado(cpfs []string, apiKey string, maxConcorrencia int) []ResultadoCPF {
 var wg sync.WaitGroup
 resultados := make([]ResultadoCPF, len(cpfs))
 semaforo := make(chan struct{}, maxConcorrencia)

 for i, cpf := range cpfs {
 wg.Add(1)
 go func(idx int, c string) {
 defer wg.Done()
 semaforo <- struct{}{} // Adquirir slot
 defer func() { <-semaforo }() // Liberar slot

 resultados[idx] = consultarCPF(c, apiKey)
 }(i, cpf)
 }

 wg.Wait()
 return resultados
}
```

| Concorrência | 100 CPFs | 1.000 CPFs | 10.000 CPFs |
|---|---|---|---|
| 1 (sequencial) | ~20s | ~200s | ~2000s |
| 10 | ~2s | ~20s | ~200s |
| 50 | ~0.4s | ~4s | ~40s |
| 100 | ~0.2s | ~2s | ~20s |

---

## Padrão worker pool com channels

O padrão worker pool distribui trabalho entre um número fixo de goroutines trabalhadoras.

```go
func workerPool(cpfs []string, apiKey string, numWorkers int) []ResultadoCPF {
 type tarefa struct {
 indice int
 cpf string
 }

 tarefas := make(chan tarefa, len(cpfs))
 resultadosChan := make(chan ResultadoCPF, len(cpfs))

 // Iniciar workers
 var wg sync.WaitGroup
 for w := 0; w < numWorkers; w++ {
 wg.Add(1)
 go func() {
 defer wg.Done()
 for t := range tarefas {
 resultado := consultarCPF(t.cpf, apiKey)
 resultadosChan <- resultado
 }
 }()
 }

 // Enviar tarefas
 for i, cpf := range cpfs {
 tarefas <- tarefa{indice: i, cpf: cpf}
 }
 close(tarefas)

 // Aguardar workers e fechar canal de resultados
 go func() {
 wg.Wait()
 close(resultadosChan)
 }()

 // Coletar resultados
 resultados := make([]ResultadoCPF, 0, len(cpfs))
 for r := range resultadosChan {
 resultados = append(resultados, r)
 }

 return resultados
}
```

---

## Errgroup para tratamento de erros

O pacote `errgroup` oferece propagação de erros e cancelamento coordenado entre goroutines. A recomendação da [OWASP](https://owasp.org/) para processamento seguro de dados pessoais inclui garantir que falhas parciais não deixem dados inconsistentes — o `errgroup` facilita esse controle ao cancelar o contexto assim que um erro crítico ocorre.

```go
import (
 "context"
 "golang.org/x/sync/errgroup"
)

func consultarLoteComErrgroup(ctx context.Context, cpfs []string, apiKey string) ([]ResultadoCPF, error) {
 resultados := make([]ResultadoCPF, len(cpfs))
 g, ctx := errgroup.WithContext(ctx)
 g.SetLimit(20) // Máximo 20 goroutines simultâneas

 for i, cpf := range cpfs {
 idx := i
 c := cpf
 g.Go(func() error {
 select {
 case <-ctx.Done():
 return ctx.Err()
 default:
 resultados[idx] = consultarCPF(c, apiKey)
 if resultados[idx].Erro == "rate_limit" {
 return fmt.Errorf("rate limit excedido para CPF %s", c)
 }
 return nil
 }
 })
 }

 if err := g.Wait(); err != nil {
 return resultados, fmt.Errorf("erro no processamento: %w", err)
 }

 return resultados, nil
}
```

**SetLimit** -- define o número máximo de goroutines concorrentes.

**Context cancelável** -- se uma goroutine falhar com erro crítico, as demais podem ser canceladas.

**Propagação de erros** -- o primeiro erro é retornado ao chamador após todas as goroutines completarem.

---

## Perguntas frequentes

### Quantas goroutines simultâneas devo usar para consultar a API de CPF?
O número ideal depende do seu volume e do plano contratado. Um bom ponto de partida é 10 a 20 goroutines simultâneas, o que já reduz o tempo de processamento de lotes em até 95% em relação à abordagem sequencial. Aumente gradualmente monitorando a latência de resposta da API e os erros de timeout.

### Como evitar erros de timeout ao processar lotes grandes de CPF?
Configure um timeout explícito no `http.Client` (recomendado: 10 segundos para absorver os ~900ms de latência com margem de segurança) e use o padrão de semáforo ou `errgroup.SetLimit` para não disparar todas as goroutines ao mesmo tempo. Para lotes acima de 1.000 CPFs, processe em chunks com pausas entre eles.

### O plano gratuito da CPFHub.io suporta processamento em lote?
Sim. O plano gratuito oferece 50 consultas por mês, sem bloqueio ao atingir o limite — consultas adicionais são cobradas a R$0,15 cada. Para testes de carga e desenvolvimento, o plano gratuito é suficiente para validar a implementação das goroutines antes de escalar para o plano Pro (1.000 consultas/mês por R$149).

### Qual padrão de concorrência é mais adequado para processar CPFs em produção?
O worker pool com channels é o padrão mais robusto para produção: controla o número exato de goroutines ativas, reutiliza workers em vez de criar novos a cada tarefa e facilita o monitoramento. O `errgroup` é preferível quando você precisa cancelar todo o lote ao detectar um erro crítico.

### Leia também

- [Diferença entre validação de CPF e consulta de CPF: quando usar cada uma](https://cpfhub.io/blog/diferenca-entre-validacao-de-cpf-e-consulta-de-cpf-quando-usar-cada-uma)
- [API de CPF grátis para desenvolvedores: como começar em 5 minutos](https://cpfhub.io/blog/api-cpf-gratis-desenvolvedores-comecar-5-minutos)
- [Onboarding digital em fintechs: como validar CPF em menos de 30 segundos](https://cpfhub.io/blog/onboarding-digital-em-fintechs-como-validar-cpf-em-menos-de-30-segundos)
- [KYC no Brasil: quais setores são obrigados a validar CPF por lei](https://cpfhub.io/blog/kyc-no-brasil-quais-setores-sao-obrigados-a-validar-cpf-por-lei)

---

## Conclusão

As goroutines de Go oferecem uma forma elegante e eficiente de processar consultas de CPF em paralelo. Desde o padrão simples com WaitGroup até o worker pool com channels e o errgroup para tratamento coordenado de erros, Go fornece primitivas poderosas para cada cenário de concorrência. A chave é escolher o nível de concorrência adequado ao rate limit da API e monitorar métricas de performance para ajustar os parâmetros.

Cadastre-se em [cpfhub.io](https://www.cpfhub.io/) — 50 consultas mensais gratuitas, sem cartão de crédito — e comece a processar lotes de CPF com alta performance usando goroutines hoje mesmo.

