O modelo de simultaneidade do Go – goroutines e canais – é o sistema de simultaneidade mais simples e poderoso em qualquer linguagem convencional. Em 2026, o Go 1.23 com iteradores de intervalo sobre função e registro estruturado de slog torna o código Go simultâneo mais limpo do que nunca. Este guia cobre tudo, desde goroutines básicas até padrões de produção.
📋 Table of Contents
Goroutines — Threads Leves
package main
import (
"fmt"
"sync"
"time"
)
func main() {
// goroutine = lightweight thread (starts with 2KB stack, not 1MB like OS threads)
go sayHello("World") // fire and forget
// Wait for goroutine using WaitGroup
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("Worker %d done
", id)
time.Sleep(time.Duration(id) * time.Millisecond)
}(i)
}
wg.Wait() // block until all goroutines finish
fmt.Println("All workers done")
}
func sayHello(name string) {
time.Sleep(10 * time.Millisecond)
fmt.Printf("Hello, %s!
", name)
}
Canais — Comunicação entre Goroutines
package main
import "fmt"
func main() {
// Unbuffered channel — sender blocks until receiver ready
ch := make(chan int)
go func() {
ch <- 42 // send
}()
value := <-ch // receive
fmt.Println(value) // 42
// Buffered channel — sender blocks only when buffer full
buffered := make(chan string, 5)
buffered <- "hello"
buffered <- "world"
// Doesn't block because buffer has space
close(buffered)
// Range over closed channel
for msg := range buffered {
fmt.Println(msg)
}
// Directional channels in function signatures
produce := func(ch chan<- int) { // send-only
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}
consume := func(ch <-chan int) { // receive-only
for n := range ch {
fmt.Println("received:", n)
}
}
dataCh := make(chan int, 10)
go produce(dataCh)
consume(dataCh)
}
Selecione – Canais de Multiplexação
package main
import (
"context"
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(100 * time.Millisecond)
ch1 <- "from ch1"
}()
go func() {
time.Sleep(200 * time.Millisecond)
ch2 <- "from ch2"
}()
// Select picks whichever channel is ready first
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
// With timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
select {
case result := <-fetchData():
fmt.Println(result)
case <-ctx.Done():
fmt.Println("timeout:", ctx.Err())
}
// Non-blocking send with default
ch := make(chan int, 1)
select {
case ch <- 42:
fmt.Println("sent")
default:
fmt.Println("channel full, skipped")
}
}
Padrão de pool de trabalhadores
package main
import (
"fmt"
"sync"
)
type Job struct {
ID int
Data string
}
type Result struct {
JobID int
Output string
Err error
}
func workerPool(numWorkers int, jobs <-chan Job, results chan<- Result) {
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
for job := range jobs {
output, err := processJob(job)
results <- Result{JobID: job.ID, Output: output, Err: err}
}
}(i)
}
// Close results channel when all workers done
go func() {
wg.Wait()
close(results)
}()
}
func processJob(job Job) (string, error) {
return fmt.Sprintf("processed: %s", job.Data), nil
}
func main() {
jobs := make(chan Job, 100)
results := make(chan Result, 100)
// Start 5 workers
go workerPool(5, jobs, results)
// Send 20 jobs
go func() {
for i := 0; i < 20; i++ {
jobs <- Job{ID: i, Data: fmt.Sprintf("item-%d", i)}
}
close(jobs)
}()
// Collect results
for result := range results {
if result.Err != nil {
fmt.Printf("Job %d failed: %v
", result.JobID, result.Err)
continue
}
fmt.Printf("Job %d: %s
", result.JobID, result.Output)
}
}
Contexto — Cancelamento e Prazos
package main
import (
"context"
"fmt"
"time"
)
func fetchWithContext(ctx context.Context, url string) (string, error) {
// Check if context already cancelled
select {
case <-ctx.Done():
return "", ctx.Err()
default:
}
// Simulate slow operation
done := make(chan string, 1)
go func() {
time.Sleep(500 * time.Millisecond)
done <- "data from " + url
}()
select {
case data := <-done:
return data, nil
case <-ctx.Done():
return "", ctx.Err()
}
}
func main() {
// Context with deadline
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
data, err := fetchWithContext(ctx, "https://api.example.com/data")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(data)
// Pass context to database queries (Go standard pattern)
// db.QueryContext(ctx, "SELECT ...")
// http.NewRequestWithContext(ctx, "GET", url, nil)
}
pacote de sincronização – Mutexes e uma vez
package main
import (
"fmt"
"sync"
"sync/atomic"
)
// Mutex for shared state
type SafeCounter struct {
mu sync.RWMutex
count map[string]int
}
func (c *SafeCounter) Increment(key string) {
c.mu.Lock()
defer c.mu.Unlock()
c.count[key]++
}
func (c *SafeCounter) Get(key string) int {
c.mu.RLock() // multiple readers allowed simultaneously
defer c.mu.RUnlock()
return c.count[key]
}
// sync.Once — run initialization exactly once
var (
dbOnce sync.Once
dbInstance *Database
)
func GetDB() *Database {
dbOnce.Do(func() {
dbInstance = &Database{} // initialized exactly once, even concurrent calls
})
return dbInstance
}
// Atomic operations — lockless for simple counters
var requestCount int64
func handleRequest() {
atomic.AddInt64(&requestCount, 1)
// Process request...
}
func getRequestCount() int64 {
return atomic.LoadInt64(&requestCount)
}
errgroup — Erros Simultâneos
package main
import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
)
func main() {
ctx := context.Background()
// Run goroutines, collect first error
g, ctx := errgroup.WithContext(ctx)
var user, posts, stats interface{}
g.Go(func() error {
var err error
user, err = fetchUser(ctx, 1)
return err
})
g.Go(func() error {
var err error
posts, err = fetchPosts(ctx, 1)
return err
})
g.Go(func() error {
var err error
stats, err = fetchStats(ctx, 1)
return err
})
// Wait for all goroutines
if err := g.Wait(); err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(user, posts, stats)
}
A simultaneidade em 2026 é elegante e poderosa quando você segue o mantra: “compartilhe memória comunicando-se, não comunique compartilhando memória”. Use goroutines livremente (elas custam aproximadamente 2 KB cada), coordene com canais e WaitGroups, lide com cancelamento de contexto em todas as operações de longa duração e evite estado mutável compartilhado (prefira canais ou operações atômicas).
🔗 Share this article
✍️ Leave a Comment