Concorrência em Go: goroutines, canais e async/await (comparação)

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Por que o Go superou todos em concorrência?

\n\n

Imagine um chef de cozinha preparando dez pratos ao mesmo tempo. Ele não corre de fogão em fogão como um louco — ele simplesmente alterna de forma eficiente entre as tarefas: enquanto a carne cozinha, corta a salada; enquanto a água ferve, embeleza o prato. No mundo da programação, isso se chama concorrência — a habilidade de gerenciar múltiplas tarefas sem esperar que cada uma termine.

\n\n

A linguagem Go (Golang) nasceu exatamente para resolver problemas de concorrência. Seus criadores no Google se cansaram das complexidades do multithreading em C++ e Java. Eles criaram as goroutines — threads leves que são iniciadas com uma única linha de código. Neste artigo, vamos explorar como a concorrência funciona em Go, compará-la com threads do SO e com o paradigma async/await do Python e, o mais importante, mostrar exemplos reais de código que você pode aplicar hoje mesmo.

\n\n

Goroutines vs Threads do SO: a batalha da cavalaria leve

\n\n

Quando um desenvolvedor ouve "multithreading", geralmente pensa em threads do sistema operacional. Mas no Go é diferente. Vamos analisar as principais diferenças.

\n\n

O que é uma Thread do SO?

\n

Uma thread é um objeto pesado gerenciado pelo kernel do SO. Cada thread tem sua própria pilha (geralmente 1-8 MB), exige chamadas de sistema para criação e alternância. Criar 10.000 threads significa sobrecarregar a memória e o processador. O Go adota uma filosofia diferente.

\n\n

Goroutine — a thread leve do Go

\n

Uma goroutine é uma função executada concorrentemente. Ela é iniciada com a palavra-chave go. Principais características:

\n
    \n
  • Tamanho da pilha: começa com 2-4 KB e cresce/encolhe dinamicamente.
  • \n
  • Criação: milhares de goroutines são criadas em milissegundos.
  • \n
  • Escalonador: o runtime do Go distribui as goroutines entre as threads do SO (geralmente igual ao número de núcleos da CPU).
  • \n
  • Sintaxe: basta adicionar go antes da chamada da função.
  • \n
\n\n
package main\n\nimport (\n    "fmt"\n    "time"\n)\n\nfunc sayHello(name string) {\n    for i := 0; i < 3; i++ {\n        fmt.Println("Olá,", name)\n        time.Sleep(100 * time.Millisecond)\n    }\n}\n\nfunc main() {\n    // Iniciando duas goroutines\n    go sayHello("Go")\n    go sayHello("Goroutine")\n\n    // Dando tempo para as goroutines executarem\n    time.Sleep(1 * time.Second)\n    fmt.Println("Função principal finalizada")\n}
\n\n

Resultado: as mensagens das duas goroutines serão intercaladas. O Go alterna automaticamente entre elas, sem bloqueio mútuo. Experimente executar este código — você verá "Olá, Go" e "Olá, Goroutine" sendo exibidos alternadamente.

\n\n

Canais: a principal ferramenta de sincronização

\n\n

Goroutines são ótimas, mas como elas se comunicam? No Go, não há memória compartilhada por padrão. Em vez disso, usa-se o princípio: "Não se comunique compartilhando memória; compartilhe memória se comunicando". Para isso, existem os canais (channels).

\n\n

Um canal é um "tubo" tipado através do qual as goroutines enviam e recebem dados. É seguro, simples e elegante.

\n\n

Exemplo: processamento paralelo de números

\n
package main\n\nimport "fmt"\n\nfunc squareWorker(numbers <-chan int, results chan<- int) {\n    for n := range numbers {\n        results <- n * n\n    }\n    close(results)\n}\n\nfunc main() {\n    numbers := make(chan int, 5)\n    results := make(chan int, 5)\n\n    // Iniciando o worker\n    go squareWorker(numbers, results)\n\n    // Enviando dados\n    numbers <- 2\n    numbers <- 4\n    numbers <- 6\n    close(numbers)\n\n    // Lendo os resultados\n    for res := range results {\n        fmt.Println("Quadrado:", res)\n    }\n}
\n\n

Neste exemplo, usamos canais com buffer (o segundo argumento é o tamanho do buffer). A goroutine worker lê do canal numbers, calcula o quadrado e envia o resultado para o canal results. O canal com buffer permite enviar dados sem que o receptor esteja imediatamente pronto, tornando a comunicação mais flexível.

Blogs

Book Recommendations