Programação

Entendendo Goroutines, Concorrência e Paralelismo em Go

08/08/2026 7 min de leitura

O git-lrc é um revisor de código com IA que analisa alterações automaticamente a cada commit, ajudando a identificar bugs e mudanças inesperadas. O projeto é gratuito, ilimitado e open source, permitindo que qualquer desenvolvedor utilize e contribua. Goroutines são funções leves do Go que permitem executar tarefas de forma concorrente e, em CPUs com múltiplos núcleos, paralela. O Go utiliza um scheduler eficiente baseado no modelo G-M-P, permitindo executar milhares de goroutines com poucas threads do sistema operacional.

Estou desenvolvendo o git-lrc, um revisor de código com IA que é executado a cada commit. É gratuito, ilimitado e o código-fonte está disponível no Github. Dê uma estrela no projeto para ajudar outros desenvolvedores a descobri-lo. Experimente e compartilhe seu feedback para que possamos aprimorá-lo.

Ao começar a usar Go, uma das coisas mais empolgantes que você encontrará são as goroutines , uma maneira simples, porém incrivelmente poderosa, de lidar com concorrência e paralelismo.

Mas o que são exatamente goroutines? Como elas funcionam? E qual a diferença real entre concorrência e paralelismo quando sua máquina possui múltiplos núcleos de CPU?

Vamos analisar tudo isso com código e conhecimento profundo.

O que são Goroutines?

Uma goroutine é uma função leve, de execução independente, gerenciada pelo ambiente de execução do Go.
Você inicia uma com a gopalavra-chave:

go myFunction() 

Essa linha diz "Go":

“Executar myFunction()em segundo plano — não bloquear a função atual.”

Ao contrário das threads do sistema operacional, as goroutines são extremamente baratas — você pode criar milhares ou até milhões delas.
Cada uma começa com uma pequena pilha (cerca de 2 KB) que cresce dinamicamente, diferentemente das pilhas de threads de tamanho fixo.

Como as Goroutines Funcionam Internamente

Go utiliza um agendador M:N , o que significa:

  • M = número de threads do sistema operacional
  • N = número de goroutines

O ambiente de execução mapeia muitas goroutines (N) em um conjunto menor de threads do sistema operacional (M) e as distribui entre os núcleos da CPU.

Ele gerencia a troca de contexto, o agendamento e a sincronização nos bastidores, sem a necessidade de lidar manualmente com pthread_create()pools de threads.

Concorrência versus Paralelismo

Esses dois conceitos são frequentemente confundidos, mas são ideias diferentes.

Prazo Significado Analogia
Concorrência Várias tarefas em andamento (não necessariamente simultâneas) Preparar o jantar enquanto checa o celular — alternando rapidamente entre as opções.
Paralelismo Várias tarefas sendo executadas literalmente ao mesmo tempo Você e um amigo cozinhando pratos diferentes em fogões separados.

As goroutines oferecem concorrência por padrão. O paralelismo
só ocorre quando vários núcleos executam goroutines simultaneamente.

Exemplo 1: Concorrência (Núcleo Único)

Eis como a concorrência se comporta quando o Go está limitado a um único núcleo de CPU:

package main import ( "fmt" "runtime" "time" ) func worker(id int) { for i := 1; i <= 3; i++ { fmt.Printf("Worker %d running iteration %d\n", id, i) time.Sleep(400 * time.Millisecond) } } func main() { runtime.GOMAXPROCS(1) // use only 1 OS thread fmt.Println("Using GOMAXPROCS:", runtime.GOMAXPROCS(0)) fmt.Println("---- Concurrency Demo ----") go worker(1) go worker(2) go worker(3) time.Sleep(3 * time.Second) } 

imagem

Embora as três goroutines estejam ativas, apenas um núcleo da CPU as executa em um dado momento.
O agendador alterna rapidamente entre elas, criando a ilusão de trabalho simultâneo.

Isso é concorrência — sobreposição no tempo, não na execução.

Exemplo 2: Paralelismo (Multi-Core)

Agora vamos ver o verdadeiro paralelismo permitindo que o Go utilize todos os núcleos de CPU disponíveis:

package main import ( "fmt" "runtime" "sync" "time" ) func heavyWork(id int, wg *sync.WaitGroup) { defer wg.Done() start := time.Now() sum := 0 for i := 0; i < 5e7; i++ { sum += i } fmt.Printf("Worker %d done in %v (sum=%d)\n", id, time.Since(start), sum) } func main() { cores := runtime.NumCPU() runtime.GOMAXPROCS(cores) fmt.Printf("Detected %d cores\n", cores) fmt.Println("---- Parallelism Demo ----") var wg sync.WaitGroup for i := 1; i <= cores; i++ { wg.Add(1) go heavyWork(i, &wg) } wg.Wait() fmt.Println("All workers done.") } 

imagem

Cada goroutine executa tarefas que exigem muito da CPU.
O agendador do Go as distribui por todos os núcleos da CPU, executando-as em verdadeiro paralelo.

Em uma CPU de 8 núcleos, o tempo total de execução será aproximadamente o tempo de uma única heavyWork()execução — não oito vezes maior.
Isso é paralelismo de verdade .

Como o Go utiliza seus núcleos

Você pode inspecionar e controlar quantos núcleos o Go utiliza:

fmt.Println(runtime.NumCPU()) // total logical CPUs fmt.Println(runtime.GOMAXPROCS(0)) // CPUs Go is currently using 

Desde o Go 1.5 , o Go utiliza automaticamente todas as CPUs lógicas disponíveis por padrão.
Você pode sobrescrever esse comportamento usando:

runtime.GOMAXPROCS(n) 

Por exemplo, configurar GOMAXPROCS(2)um sistema de 8 núcleos significa que o Go irá agendar as goroutines em apenas 2 núcleos.

Cargas de trabalho limitadas pela CPU versus cargas de trabalho limitadas por E/S

Quando é que núcleos extras realmente ajudam? Depende da sua carga de trabalho:

Tipo Descrição Exemplos Benefícios de múltiplos núcleos
limitado pela CPU Computação pesada matemática, compressão, processamento de imagens ✅ Aumento significativo de velocidade
Ligado a E/S Aguardando E/S de rede/disco Chamadas de API, consultas de banco de dados, entrada/saída de arquivos ⚙️ Limitado (principalmente concorrência)
Misturado Computação + E/S API + pós-processamento ⚡ Bom

Portanto, mais núcleos só são úteis quando suas goroutines estão realmente executando tarefas na CPU, e não apenas aguardando.

Aguardando a conclusão das Goroutines

Por padrão, a main()função termina imediatamente, encerrando quaisquer goroutines em execução.

Use sync.WaitGrouppara aguardar até que tudo esteja concluído:

var wg sync.WaitGroup for i := 1; i <= 5; i++ { wg.Add(1) go func(id int) { defer wg.Done() fmt.Println("Worker", id, "done") }(i) } wg.Wait() // blocks until all goroutines complete 

Como funciona o Go Scheduler (Análise Detalhada)

O agendador de tempo de execução do Go gira em torno de três componentes:

Prazo Significado
G Goroutine (sua tarefa leve)
M Máquina (um thread do SO)
P Processador (um contexto lógico que agenda Gs em Ms)

Sempre há GOMAXPROCSum número de Ps.
Cada P gerencia um M e executa vários Gs usando uma fila local.
Se um P ficar sem trabalho, ele " rouba " goroutines de outro — isso é roubo de trabalho , mantendo as CPUs ocupadas de forma eficiente.

É assim que o Go consegue lidar com milhões de goroutines em poucas threads do sistema operacional.

Conclusão

Goroutines são o superpoder do Go, tornando a programação concorrente e paralela simples e eficiente.

  • Utilize goroutines para concorrência (várias tarefas "em andamento").
  • UtilizadoGOMAXPROCS(runtime.NumCPU()) para paralelismo (todos os núcleos ativos)
  • Combine ambos para obter escalabilidade massiva.

Mesmo com milhões de goroutines, o Go mantém-se eficiente graças ao seu agendador de roubo de trabalho , dimensionamento dinâmico da pilha e modelo de threading M:N .

git-lrc
*Os agentes de IA escrevem código rapidamente. Eles também removem lógica silenciosamente, alteram comportamentos e introduzem bugs — sem que você perceba. Muitas vezes, você só descobre isso em produção.

O git-lrc resolve isso. Ele se integra ao comando `git commit` e revisa cada diferença antes que ela seja aplicada. Configuração em 60 segundos. Completamente gratuito.*

Qualquer feedback ou contribuição é bem-vindo! Está online, o código-fonte está disponível e pronto para qualquer pessoa usar.

Comentários

Ainda não há comentários. Seja o primeiro.

Escreva um comentário

Compartilhar

Assistente IA
Online agora

Olá! 👋 Sou o assistente virtual da EasyEconTech. Como posso ajudá-lo hoje?

Agora