<h2>O que são Goroutines</h2>
<p>Goroutines são funções que executam de forma concorrente dentro do mesmo espaço de memória da aplicação Go. Diferentemente de threads do sistema operacional, que consomem recursos significativos, goroutines são extremamente leves — você pode criar milhares delas sem degradação de performance. Elas são gerenciadas pelo runtime do Go, que é responsável por agendar sua execução nos processadores disponíveis.</p>
<p>A palavra-chave <code>go</code> é tudo que você precisa para iniciar uma goroutine. Quando você prefixar uma chamada de função com <code>go</code>, o runtime enfileira essa função para execução concorrente e retorna imediatamente ao código que a chamou. Isso permite que seu programa continue executando outras operações enquanto aquela goroutine roda em paralelo.</p>
<pre><code class="language-go">package main
import (
"fmt"
"time"
)
func sayHello(name string) {
for i := 1; i <= 3; i++ {
fmt.Printf("Olá %s #%d\n", name, i)
time.Sleep(100 * time.Millisecond)
}
}
func main() {
go sayHello("Alice")
go sayHello("Bob")
// Sem sleep, o programa encerraria antes das goroutines terminarem
time.Sleep(1 * time.Second)
fmt.Println("Programa finalizado")
}</code></pre>
<p>Neste exemplo, duas goroutines executam simultaneamente, mas não há verdadeiro paralelismo se você tiver apenas um núcleo de processador. O runtime alterna entre elas, permitindo concorrência. O <code>time.Sleep</code> no <code>main</code> é uma solução temporária — mais adiante você aprenderá padrões mais elegantes.</p>
<h2>Criação e Sincronização de Goroutines</h2>
<h3>Canais: A Forma Correta de Sincronizar</h3>
<p>Canais são o mecanismo principal de comunicação entre goroutines em Go. Um canal é um tipo que permite que uma goroutine envie um valor que outra goroutine recebe. Quando você tenta receber de um canal vazio, a goroutine fica bloqueada até que algo seja enviado — esse é o segredo para sincronização sem usar locks explícitos.</p>
<pre><code class="language-go">package main
import (
"fmt"
)
func worker(id int, results chan string) {
// Simula algum trabalho
fmt.Printf("Worker %d iniciado\n", id)
results <- fmt.Sprintf("Worker %d completou", id)
}
func main() {
results := make(chan string, 3)
go worker(1, results)
go worker(2, results)
go worker(3, results)
// Bloqueia até receber 3 mensagens
for i := 0; i < 3; i++ {
fmt.Println(<-results)
}
fmt.Println("Todos os workers terminaram")
}</code></pre>
<p>Quando você cria um canal com <code>make(chan string, 3)</code>, o <code>3</code> é a capacidade do buffer. Com buffer, a goroutine pode enviar até 3 valores sem bloquear. Se a capacidade fosse 0 (ou omitida), o canal seria unbuffered, e o remetente bloquearia até que alguém recebesse.</p>
<h3>WaitGroup: Padrão Padrão para Múltiplas Goroutines</h3>
<p>Para cenários onde você tem um número variável de goroutines e quer esperar que todas terminem, <code>sync.WaitGroup</code> é mais elegante que usar canais. É um contador que você incrementa quando uma goroutine inicia e decrementa quando termina.</p>
<pre><code class="language-go">package main
import (
"fmt"
"sync"
"time"
)
func processItem(id int, wg *sync.WaitGroup) {
defer wg.Done() // Decrementa o contador ao final
fmt.Printf("Processando item %d\n", id)
time.Sleep(500 * time.Millisecond)
fmt.Printf("Item %d completo\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1) // Incrementa o contador
go processItem(i, &wg)
}
wg.Wait() // Bloqueia até o contador chegar a zero
fmt.Println("Todos os itens foram processados")
}</code></pre>
<p>Use <code>WaitGroup</code> quando o trabalho é homogêneo (você não precisa receber diferentes tipos de resultados). Use canais quando precisa de comunicação bidirecional ou quando diferentes goroutines produzem resultados diferentes.</p>
<h2>Escalonamento e o Modelo M:N do Runtime</h2>
<h3>Como Go Agenda Goroutines</h3>
<p>Go não usa uma goroutine por thread do SO. Em vez disso, implementa um modelo M:N, onde M goroutines são multiplexadas em N threads do sistema operacional. Internamente, o scheduler do Go mantém uma fila de goroutines prontas e as distribui entre os workers threads. Quando uma goroutine bloqueia em uma operação (como I/O de rede ou arquivo), o scheduler move outra goroutine pronta para aquela thread.</p>
<p>O scheduler do Go é preemptivo apenas em pontos específicos: chamadas para funções da biblioteca padrão, operações de I/O, e alocação de memória. Se uma goroutine executar um loop infinito sem yield, ela pode bloquear a thread. Felizmente, na prática isso é raro porque operações reais (como HTTP requests) causam I/O e liberam a thread.</p>
<pre><code class="language-go">package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func printStats() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine())
fmt.Printf("Threads: %d\n", runtime.NumCPU())
fmt.Printf("Memória Alocada: %v MB\n", m.Alloc/1024/1024)
}
func dummyWork(id int, wg *sync.WaitGroup) {
defer wg.Done()
time.Sleep(1 * time.Second)
}
func main() {
fmt.Println("Estado inicial:")
printStats()
var wg sync.WaitGroup
count := 10000
for i := 0; i < count; i++ {
wg.Add(1)
go dummyWork(i, &wg)
}
fmt.Println("\nCom 10k goroutines:")
printStats()
wg.Wait()
fmt.Println("\nApós conclusão:")
printStats()
}</code></pre>
<p>Execute este código e observe o número de goroutines crescer. Você verá que 10.000 goroutines ocupam poucos MB de RAM — isso demonstra a eficiência do modelo M:N. Cada goroutine consome aproximadamente 2-4 KB de stack inicial.</p>
<h3>GOMAXPROCS: Controlando Paralelismo</h3>
<p>A variável de ambiente <code>GOMAXPROCS</code> controla quantas threads do SO o Go pode usar simultaneamente. Por padrão, Go utiliza todos os núcleos disponíveis. Você pode também ajustar isso em tempo de execução com <code>runtime.GOMAXPROCS()</code>.</p>
<pre><code class="language-go">package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func cpuIntensiveTask(id int, wg *sync.WaitGroup) {
defer wg.Done()
// Simula tarefa que consome CPU
sum := 0
for i := 0; i < 1000000000; i++ {
sum += i
}
fmt.Printf("Task %d completada\n", id)
}
func main() {
// Descobre núcleos disponíveis
numCPU := runtime.NumCPU()
fmt.Printf("CPUs disponíveis: %d\n", numCPU)
// Ajusta GOMAXPROCS para usar apenas 1 núcleo (descomente para testar)
// runtime.GOMAXPROCS(1)
var wg sync.WaitGroup
start := time.Now()
for i := 0; i < 4; i++ {
wg.Add(1)
go cpuIntensiveTask(i, &wg)
}
wg.Wait()
fmt.Printf("Tempo total: %v\n", time.Since(start))
}</code></pre>
<p>Tarefas CPU-bound (que não bloqueiam) realmente se beneficiam de paralelismo. Se você executar o código acima com <code>GOMAXPROCS(1)</code> e depois com todos os núcleos, verá uma diferença significativa no tempo de execução.</p>
<h2>Padrões Avançados e Armadilhas Comuns</h2>
<h3>Padrão: Fan-Out / Fan-In</h3>
<p>Um padrão poderoso é distribuir trabalho para múltiplas goroutines (fan-out) e depois coletar os resultados (fan-in). Isso é útil para paralelizar processamento de uma lista.</p>
<pre><code class="language-go">package main
import (
"fmt"
"sync"
)
func square(numbers <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for n := range numbers {
results <- n * n
}
}
func main() {
numbers := make(chan int, 5)
results := make(chan int)
// Enche o canal com números
go func() {
for i := 1; i <= 5; i++ {
numbers <- i
}
close(numbers)
}()
// Fan-out: 3 workers processam números em paralelo
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go square(numbers, results, &wg)
}
// Fan-in: coleta resultados
go func() {
wg.Wait()
close(results)
}()
// Consome resultados
for result := range results {
fmt.Println(result)
}
}</code></pre>
<h3>Armadilha: Goroutine Leak</h3>
<p>Uma goroutine leak ocorre quando uma goroutine é criada mas nunca termina, consumindo recursos indefinidamente. A causa comum é uma goroutine bloqueada em um canal do qual nunca receberá dados.</p>
<pre><code class="language-go"></code></pre>
<h3>Padrão: Context para Cancelamento</h3>
<p>Para aplicações mais sofisticadas, use <code>context.Context</code> para propagar sinais de cancelamento através de toda a árvore de goroutines.</p>
<pre><code class="language-go">package main
import (
"context"
"fmt"
"time"
)
func worker(ctx context.Context, id int) {
for {
select {
case <-ctx.Done():
fmt.Printf("Worker %d cancelado: %v\n", id, ctx.Err())
return
default:
fmt.Printf("Worker %d trabalhando...\n", id)
time.Sleep(500 * time.Millisecond)
}
}
}
func main() {
// Cria context que será cancelado em 2 segundos
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
for i := 1; i <= 3; i++ {
go worker(ctx, i)
}
// Espera todas as goroutines terminarem
time.Sleep(3 * time.Second)
fmt.Println("Programa finalizado")
}</code></pre>
<h2>Conclusão</h2>
<p>Você aprendeu que goroutines são abstrações leves gerenciadas pelo runtime que multiplicam a eficiência da concorrência em Go através do modelo M:N, permitindo criar milhares delas sem o overhead de threads tradicionais. O mecanismo de sincronização principal é o canal, que força você a pensar sobre comunicação entre goroutines de forma segura por padrão — não há race conditions silenciosas como em linguagens que dependem de locks.</p>
<p>O runtime do Go é responsável por agendar essas goroutines nos núcleos disponíveis de forma preemptiva e eficiente, bloqueando quando necessário (I/O) e desbloqueando quando é seguro continuar. Domine canais, <code>WaitGroup</code> e <code>context.Context</code>, e você terá as ferramentas para construir sistemas altamente concorrentes — desde servidores web até processadores de dados paralelos — com código limpo e confiável.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://golang.org/doc/effective_go#concurrency" target="_blank" rel="noopener noreferrer">Effective Go — Concurrency</a></li>
<li><a href="https://go.dev/blog/pipelines" target="_blank" rel="noopener noreferrer">Go Blog: Goroutines</a></li>
<li><a href="https://www.gopl.io/" target="_blank" rel="noopener noreferrer">The Go Programming Language — Capítulo 8 (Goroutines e Channels)</a></li>
<li><a href="https://github.com/golang/go/tree/master/src/runtime" target="_blank" rel="noopener noreferrer">Go Runtime Source Code</a></li>
<li><a href="https://www.oreilly.com/library/view/concurrency-in-go/9781491941874/" target="_blank" rel="noopener noreferrer">Concurrency in Go: Tools and Techniques for Developers</a></li>
</ul>
<p><!-- FIM --></p>