O que é Goroutine em Go?

Uma goroutine é uma função executada de forma concorrente pelo runtime do Go. É o mecanismo fundamental de concorrência da linguagem e uma das características que tornam Go especialmente poderosa para aplicações que precisam lidar com múltiplas tarefas simultaneamente, como servidores web, APIs e sistemas distribuídos.

Diferente de threads tradicionais do sistema operacional, goroutines são extremamente leves. Enquanto uma thread do SO consome tipicamente entre 1 MB e 8 MB de memória de stack, uma goroutine começa com apenas 2 KB a 8 KB de stack, que cresce e diminui dinamicamente conforme a necessidade. Isso significa que é perfeitamente viável rodar centenas de milhares — e até milhões — de goroutines em um único processo Go.

Quando você inicia um programa Go, a função main() já roda dentro de uma goroutine — a goroutine principal. A partir dela, você pode disparar quantas goroutines adicionais precisar usando a palavra-chave go.

Sintaxe básica

Para criar uma goroutine, basta usar a palavra-chave go seguida de uma chamada de função:

package main

import (
    "fmt"
    "time"
)

func saudacao(nome string) {
    fmt.Printf("Olá, %s!\n", nome)
}

func main() {
    go saudacao("Maria")   // executa em uma goroutine separada
    go saudacao("João")    // outra goroutine

    // Sem essa pausa, main() terminaria antes das goroutines
    time.Sleep(100 * time.Millisecond)
}

Também é muito comum usar funções anônimas (closures) como goroutines:

go func() {
    fmt.Println("Executando em goroutine anônima")
}()

Note o () no final — é necessário para invocar a função imediatamente.

Como goroutines funcionam internamente

O runtime do Go implementa um modelo de escalonamento conhecido como M:N scheduling, onde M goroutines são multiplexadas sobre N threads do sistema operacional. Os três componentes principais são:

  • G (Goroutine): a unidade de execução concorrente
  • M (Machine): a thread do SO que efetivamente executa código
  • P (Processor): o processador lógico que conecta G a M, mantendo uma fila local de goroutines prontas para execução

Quando uma goroutine faz uma operação bloqueante (como I/O de rede), o runtime automaticamente desanexa a goroutine da thread do SO e reaproveita essa thread para executar outra goroutine pronta. Quando a operação bloqueante completa, a goroutine é colocada de volta na fila de execução. Esse modelo é chamado de preemptive scheduling e foi significativamente melhorado a partir do Go 1.14.

Stack dinâmica

A stack de uma goroutine começa pequena e cresce sob demanda. Quando a stack de uma goroutine atinge seu limite, o runtime aloca uma nova stack maior, copia os dados e continua a execução. Esse processo é transparente para o programador, e a stack pode crescer até valores como 1 GB (configurável via runtime.SetMaxStack).

Goroutine vs Thread

AspectoGoroutineThread do SO
Memória inicial2-8 KB1-8 MB
CriaçãoMicrosegundosMilissegundos
EscalonamentoRuntime do Go (user-space)Kernel do SO
Quantidade práticaCentenas de milharesMilhares
StackDinâmica, cresce sob demandaFixa na criação
ComunicaçãoChannelsLocks, mutexes

Padrões comuns com goroutines

Worker pool

O padrão worker pool é uma das formas mais eficientes de processar tarefas em paralelo com controle de concorrência:

package main

import (
    "fmt"
    "sync"
)

func worker(id int, tarefas <-chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    for tarefa := range tarefas {
        fmt.Printf("Worker %d processando tarefa %d\n", id, tarefa)
    }
}

func main() {
    const numWorkers = 3
    tarefas := make(chan int, 10)
    var wg sync.WaitGroup

    // Inicia workers
    for i := 1; i <= numWorkers; i++ {
        wg.Add(1)
        go worker(i, tarefas, &wg)
    }

    // Envia tarefas
    for t := 1; t <= 20; t++ {
        tarefas <- t
    }
    close(tarefas)

    wg.Wait()
    fmt.Println("Todas as tarefas concluídas")
}

Fan-out / Fan-in

Esse padrão distribui trabalho entre múltiplas goroutines (fan-out) e depois coleta os resultados em um único canal (fan-in). É especialmente útil para processamento paralelo de dados. Veja mais detalhes no artigo sobre padrões de concorrência em Go.

Cuidados importantes

Evite vazamento de goroutines

Uma goroutine que nunca termina consome memória indefinidamente. Use context.Context para cancelar goroutines quando não são mais necessárias:

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

go func(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            return // goroutine encerra corretamente
        default:
            // trabalho...
        }
    }
}(ctx)

A partir do Go 1.26, o pacote testing/goroutineleak permite detectar vazamentos de goroutines automaticamente nos seus testes.

Sempre sincronize o acesso a dados compartilhados

Goroutines que acessam os mesmos dados sem sincronização causam race conditions. Use channels para comunicação ou sync.Mutex para proteger dados compartilhados. O Go oferece o detector de corridas (go run -race) para identificar esses problemas durante o desenvolvimento.

Perguntas Frequentes

Qual a diferença entre goroutine e thread?

Goroutines são gerenciadas pelo runtime do Go em user-space, enquanto threads são gerenciadas pelo kernel do sistema operacional. Goroutines usam significativamente menos memória (2-8 KB vs 1-8 MB), são criadas mais rapidamente (microsegundos vs milissegundos) e podem existir em quantidades muito maiores. O runtime do Go multiplexa milhares de goroutines sobre um número menor de threads do SO.

Quantas goroutines posso criar?

Na prática, você pode criar centenas de milhares ou até milhões de goroutines. O limite depende da memória disponível. Cada goroutine consome apenas 2-8 KB de stack inicialmente. Em um servidor com 8 GB de RAM, teoricamente seria possível ter mais de 1 milhão de goroutines ativas, embora em cenários reais o número dependa do que cada goroutine faz.

Como espero uma goroutine terminar?

A forma mais comum é usar sync.WaitGroup para esperar múltiplas goroutines, ou usar um channel para receber o resultado. Nunca use time.Sleep em código de produção para esperar goroutines — use mecanismos de sincronização explícitos.

Goroutines rodam em paralelo?

Goroutines são concorrentes, mas só rodam em paralelo se houver múltiplos núcleos de CPU disponíveis e se GOMAXPROCS estiver configurado para usar mais de um processador (o padrão desde o Go 1.5 é o número de CPUs disponíveis). Concorrência é sobre estrutura; paralelismo é sobre execução simultânea real.

Veja também