Pacote sync

Lição 16: Pacote sync — O Guardião da Segurança de Concorrência

Analogia

Imagine um banheiro público:


Conceitos Fundamentais

1. Por que Precisamos do Pacote sync?

As goroutines do Go são leves e poderosas, mas quando múltiplas goroutines acessam dados compartilhados simultaneamente, ocorrem condições de corrida:

GO
// Perigoso! Múltiplas goroutines modificando count simultaneamente
var count int
for i := 0; i < 1000; i++ {
    go func() {
        count++ // Corrida de dados!
    }()
}

O pacote sync fornece primitivas de sincronização para garantir segurança de concorrência.

2. Visão Geral dos Tipos Principais

Tipo Propósito Características
sync.Mutex Exclusão mútua Apenas uma goroutine pode mantê-la por vez
sync.RWMutex Lock de leitura-escrita Múltiplos leitores, escritor único; leitores não conflitam
sync.WaitGroup Grupo de espera Espera um grupo de goroutines completar (coberto na Lição 15)
sync.Once Execução única Garante que uma função execute apenas uma vez
sync.Pool Pool de objetos Reutiliza objetos, reduz alocações de memória
sync.Map Mapa seguro para concorrência Armazenamento key-value concorrente sem lock
sync.Cond Variável de condição Notificação de condição entre goroutines
Pacote atomic Operações atômicas Operações de concorrência seguras de nível inferior e mais eficientes

Sintaxe Básica e Uso

💡 Mutex

GO
package main

import (
    "fmt"
    "sync"
)

func main() {
    var mu sync.Mutex
    count := 0
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            mu.Lock()         // Trava
            count++           // Modifica variável compartilhada com segurança
            mu.Unlock()       // Destrava
        }()
    }

    wg.Wait()
    fmt.Println("count =", count) // Saída: count = 1000
}
💡 Dica: Usar defer mu.Unlock() garante que o lock seja liberado mesmo se a função causar panic, prevenindo deadlocks.

💡 RWMutex (Lock de Leitura-Escrita)

GO
package main

import (
    "fmt"
    "sync"
)

func main() {
    var rwmu sync.RWMutex
    data := make(map[string]string)
    var wg sync.WaitGroup

    // Operação de escrita: usa lock de escrita
    wg.Add(1)
    go func() {
        defer wg.Done()
        rwmu.Lock() // Lock de escrita: exclusivo
        data["key"] = "value"
        rwmu.Unlock()
    }()

    // Operação de leitura: usa lock de leitura (pode ser concorrente)
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            rwmu.RLock() // Lock de leitura: compartilhado
            _ = data["key"]
            rwmu.RUnlock()
        }()
    }

    wg.Wait()
    fmt.Println("data:", data)
}
💡 Dica: Para cenários com muita leitura e pouca escrita, RWMutex tem melhor desempenho; se as frequências de leitura e escrita são similares, Mutex é mais simples.

💡 Once (Execução Única)

GO
package main

import (
    "fmt"
    "sync"
)

func main() {
    var once sync.Once
    var wg sync.WaitGroup

    setup := func() {
        fmt.Println("Inicialização (executa apenas uma vez)")
    }

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            once.Do(setup) // Apenas a primeira goroutine vai executar setup
            fmt.Printf("goroutine %d concluída\n", id)
        }(i)
    }

    wg.Wait()
}
💡 Dica: Once garante que mesmo que múltiplas goroutines a chamem simultaneamente, a função fornecida só será executada uma vez. Comumente usada para inicialização de singleton.

💡 Pool (Pool de Objetos)

GO
package main

import (
    "fmt"
    "sync"
)

func main() {
    pool := &sync.Pool{
        New: func() interface{} {
            fmt.Println("Criando novo objeto")
            return make([]byte, 1024)
        },
    }

    // Primeiro Get: chama New para criar
    buf1 := pool.Get().([]byte)
    fmt.Println("Objeto obtido, comprimento:", len(buf1))

    // Retorna após uso
    pool.Put(buf1)

    // Segundo Get: reutiliza o objeto retornado anteriormente
    buf2 := pool.Get().([]byte)
    fmt.Println("Objeto reutilizado, comprimento:", len(buf2))
}
💡 Dica: Objetos no Pool podem ser coletados pelo garbage collector durante qualquer ciclo de GC. Não assuma que um objeto colocado com Put sempre pode ser recuperado com Get.

💡 Operações Atômicas

GO
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

func main() {
    var count int64 = 0
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            atomic.AddInt64(&count, 1) // Incremento atômico
        }()
    }

    wg.Wait()
    fmt.Println("count =", atomic.LoadInt64(&count)) // Leitura atômica
}
💡 Dica: Operações atomic são mais leves que Mutex, adequadas para contadores simples, flags, etc.

💡 Detecção de Corrida

BASH
go run -race main.go    # Detecta corridas em tempo de execução
go test -race ./...     # Detecta corridas durante testes
💡 Dica: Recomenda-se sempre habilitar -race durante o desenvolvimento; ajuda a descobrir problemas ocultos de corrida de dados.


Exemplos Práticos

Exemplo: Contador Thread-safe (Dificuldade ⭐)

Um contador seguro encapsulado com mutex:

GO
package main

import (
    "fmt"
    "sync"
)

// SafeCounter é um contador thread-safe
type SafeCounter struct {
    mu    sync.Mutex
    count int
}

// Inc incrementa o contador
func (c *SafeCounter) Inc() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

// Value retorna o valor atual
func (c *SafeCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

func main() {
    counter := &SafeCounter{}
    var wg sync.WaitGroup

    // Inicia 100 goroutines, cada uma incrementando 100 vezes
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for j := 0; j < 100; j++ {
                counter.Inc()
            }
        }()
    }

    wg.Wait()
    fmt.Println("Contagem final:", counter.Value()) // Saída: Contagem final: 10000
}
▶ Experimente

Exemplo: Cache Concorrente Seguro com sync.Map (Dificuldade ⭐⭐)

GO
package main

import (
    "fmt"
    "sync"
)

// Cache é um cache concorrente seguro
type Cache struct {
    store sync.Map
}

// Set armazena um valor no cache
func (c *Cache) Set(key string, value interface{}) {
    c.store.Store(key, value)
}

// Get recupera um valor do cache
func (c *Cache) Get(key string) (interface{}, bool) {
    c.store.Load(key)
}

// Delete remove uma chave do cache
func (c *Cache) Delete(key string) {
    c.store.Delete(key)
}

// GetOrSet recupera ou define um valor atomicamente (evita computação redundante)
func (c *Cache) GetOrSet(key string, factory func() interface{}) interface{} {
    if val, ok := c.store.Load(key); ok {
        return val
    }
    // Usa LoadOrStore para garantir atomicidade
    val, _ := c.store.LoadOrStore(key, factory())
    return val
}

func main() {
    cache := &Cache{}
    var wg sync.WaitGroup

    // Escritas concorrentes
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            key := fmt.Sprintf("key-%d", id)
            cache.Set(key, id*10)
            fmt.Printf("Escrita: %s = %d\n", key, id*10)
        }(i)
    }

    wg.Wait()

    // Leituras concorrentes
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            key := fmt.Sprintf("key-%d", id)
            if val, ok := cache.Get(key); ok {
                fmt.Printf("Leitura: %s = %v\n", key, val)
            }
        }(i)
    }

    wg.Wait()

    // Usa GetOrSet para evitar computação redundante
    result := cache.GetOrSet("computed", func() interface{} {
        fmt.Println("Realizando computação complexa...")
        return 42
    })
    fmt.Println("computed =", result)

    // Obtém novamente, não vai recomputar
    result2 := cache.GetOrSet("computed", func() interface{} {
        fmt.Println("Esta linha não vai executar")
        return 99
    })
    fmt.Println("computed =", result2)
}
▶ Experimente

Exemplo: Pool de Workers com Timeout (Uso Abrangente de sync) (Dificuldade ⭐⭐⭐)

GO
package main

import (
    "context"
    "fmt"
    "math/rand"
    "sync"
    "sync/atomic"
    "time"
)

// Task representa uma tarefa
type Task struct {
    ID   int
    Data string
}

// Result representa um resultado
type Result struct {
    TaskID   int
    Output   string
    WorkerID int
    Duration time.Duration
}

// WorkerPool é um pool de workers
type WorkerPool struct {
    workerCount int
    taskCh      chan Task
    resultCh    chan Result
    wg          sync.WaitGroup
    processed   int64 // contador atômico
    errors      int64
    once        sync.Once // garante inicialização única
    pool        sync.Pool // reutiliza objetos Result
}

// NewWorkerPool cria um pool de workers
func NewWorkerPool(workerCount, taskBufferSize int) *WorkerPool {
    wp := &WorkerPool{
        workerCount: workerCount,
        taskCh:      make(chan Task, taskBufferSize),
        resultCh:    make(chan Result, taskBufferSize),
    }

    // Inicializa pool de objetos
    wp.pool = sync.Pool{
        New: func() interface{} {
            return &Result{}
        },
    }

    return wp
}

// Start inicia o pool de workers (executa apenas uma vez)
func (wp *WorkerPool) Start(ctx context.Context) {
    wp.once.Do(func() {
        for i := 0; i < wp.workerCount; i++ {
            wp.wg.Add(1)
            go wp.worker(ctx, i)
        }
        fmt.Printf("Pool de workers iniciado com %d workers\n", wp.workerCount)
    })
}

// worker é a goroutine worker
func (wp *WorkerPool) worker(ctx context.Context, id int) {
    defer wp.wg.Done()

    for {
        select {
        case <-ctx.Done():
            fmt.Printf("Worker %d: sinal de saída recebido\n", id)
            return
        case task, ok := <-wp.taskCh:
            if !ok {
                fmt.Printf("Worker %d: channel de tarefas fechado\n", id)
                return
            }
            // Obtém Result do pool
            result := wp.pool.Get().(*Result)
            result.TaskID = task.ID
            result.WorkerID = id

            // Simula processamento de tarefa
            start := time.Now()
            time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
            result.Output = fmt.Sprintf("Processado: %s", task.Data)
            result.Duration = time.Since(start)

            atomic.AddInt64(&wp.processed, 1)
            wp.resultCh <- *result

            // Retorna objeto Result ao pool (nota: envia uma cópia por valor)
            wp.pool.Put(result)
        }
    }
}

// Submit submete uma tarefa
func (wp *WorkerPool) Submit(task Task) {
    wp.taskCh <- task
}

// Close fecha o pool de workers
func (wp *WorkerPool) Close() {
    close(wp.taskCh)
    wp.wg.Wait()
    close(wp.resultCh)
}

// Stats retorna estatísticas
func (wp *WorkerPool) Stats() (processed, errors int64) {
    return atomic.LoadInt64(&wp.processed), atomic.LoadInt64(&wp.errors)
}

func main() {
    rand.Seed(time.Now().UnixNano())

    // Cria contexto com timeout
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    // Cria pool de workers
    pool := NewWorkerPool(3, 20)
    pool.Start(ctx)

    // Submete tarefas
    var submitWg sync.WaitGroup
    submitWg.Add(1)
    go func() {
        defer submitWg.Done()
        for i := 1; i <= 10; i++ {
            pool.Submit(Task{
                ID:   i,
                Data: fmt.Sprintf("task-data-%d", i),
            })
            fmt.Printf("Tarefa #%d submetida\n", i)
        }
    }()

    // Coleta resultados
    var collectWg sync.WaitGroup
    collectWg.Add(1)
    go func() {
        defer collectWg.Done()
        for result := range pool.resultCh {
            fmt.Printf("Tarefa#%d processada por Worker%d, levou %v -> %s\n",
                result.TaskID, result.WorkerID, result.Duration, result.Output)
        }
    }()

    // Espera submissão de tarefas completar
    submitWg.Wait()

    // Fecha pool de workers
    pool.Close()

    // Espera coleta de resultados completar
    collectWg.Wait()

    // Exibe estatísticas
    processed, _ := pool.Stats()
    fmt.Printf("\nEstatísticas: %d tarefas processadas no total\n", processed)
}
▶ Experimente

Casos de Uso Práticos

Caso 1: Limitação de Taxa para Requisições HTTP Concorrentes

Em crawlers ou chamadas de API, você precisa limitar a concorrência para evitar ser banido:

GO
package main

import (
    "fmt"
    "sync"
    "time"
)

// RateLimiter é um limitador de taxa de concorrência simples
type RateLimiter struct {
    sem     chan struct{}
    mu      sync.Mutex
    active  int
    maxConc int
}

// NewRateLimiter cria um limitador de taxa; maxConc é a concorrência máxima
func NewRateLimiter(maxConc int) *RateLimiter {
    return &RateLimiter{
        sem:     make(chan struct{}, maxConc),
        maxConc: maxConc,
    }
}

// Acquire adquire uma permissão
func (r *RateLimiter) Acquire() {
    r.sem <- struct{}{}
    r.mu.Lock()
    r.active++
    r.mu.Unlock()
}

// Release libera uma permissão
func (r *RateLimiter) Release() {
    <-r.sem
    r.mu.Lock()
    r.active--
    r.mu.Unlock()
}

// ActiveCount retorna a contagem ativa atual
func (r *RateLimiter) ActiveCount() int {
    r.mu.Lock()
    defer r.mu.Unlock()
    return r.active
}

func main() {
    limiter := NewRateLimiter(3) // máximo 3 concorrentes
    var wg sync.WaitGroup

    urls := []string{
        "https://api.example.com/page1",
        "https://api.example.com/page2",
        "https://api.example.com/page3",
        "https://api.example.com/page4",
        "https://api.example.com/page5",
        "https://api.example.com/page6",
    }

    for _, url := range urls {
        wg.Add(1)
        go func(u string) {
            defer wg.Done()

            limiter.Acquire()        // adquire permissão (bloqueia quando mais de 3)
            defer limiter.Release()  // libera permissão

            fmt.Printf("[Concorrente:%d] Iniciando requisição: %s\n", limiter.ActiveCount(), u)
            time.Sleep(time.Duration(100+len(u)*10) * time.Millisecond) // simula requisição
            fmt.Printf("[Concorrente:%d] Requisição concluída: %s\n", limiter.ActiveCount(), u)
        }(url)
    }

    wg.Wait()
    fmt.Println("Todas as requisições concluídas")
}

Caso 2: Carregamento Preguiçoso de Configuração (Padrão Singleton)

Use sync.Once para garantir que a configuração seja carregada apenas uma vez:

GO
package main

import (
    "fmt"
    "sync"
)

// Config é a configuração da aplicação
type Config struct {
    DatabaseURL string
    APIKey      string
    MaxRetries  int
}

var (
    config *Config
    once   sync.Once
)

// GetConfig obtém a configuração (carregamento preguiçoso, inicializa apenas uma vez)
func GetConfig() *Config {
    once.Do(func() {
        fmt.Println("Carregando configuração (executa apenas uma vez)...")
        // Simula carregamento de config de arquivo ou variáveis de ambiente
        config = &Config{
            DatabaseURL: "postgres://localhost:5432/mydb",
            APIKey:      "sk-xxxxxxxxxxxx",
            MaxRetries:  3,
        }
    })
    return config
}

func main() {
    var wg sync.WaitGroup

    // Múltiplas goroutines obtendo config simultaneamente
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            cfg := GetConfig()
            fmt.Printf("Goroutine %d: DB=%s, Retries=%d\n",
                id, cfg.DatabaseURL, cfg.MaxRetries)
        }(i)
    }

    wg.Wait()
    fmt.Println("Carregamento de configuração concluído")
}

❓ Perguntas Frequentes

P1: Como escolher entre Mutex e RWMutex?

Cenário Recomendação
Muita leitura, pouca escrita (ex: cache) RWMutex — múltiplas leituras podem ser concorrentes
Leitura/escrita balanceada ou muita escrita Mutex — mais simples, menor overhead
Contador simples atomic — mais leve
GO
// ❌ Usando Mutex para cenários de muita leitura, pouca escrita — desempenho ruim
var mu sync.Mutex
mu.Lock()
val := cache[key] // operação de leitura também exclusiva
mu.Unlock()

// ✅ Use RWMutex — operações de leitura podem ser concorrentes
var rwmu sync.RWMutex
rwmu.RLock()
val := cache[key] // múltiplas goroutines podem ler simultaneamente
rwmu.RUnlock()

P2: Como escolher entre sync.Map e um mapa com lock?

GO
// Cenário 1: Pares key-value são relativamente estáticos, muita leitura, pouca escrita -> sync.Map
var m sync.Map
m.Store("key", "value")
val, _ := m.Load("key")

// Cenário 2: Adição/exclusão frequente, precisa de iteração -> mapa com lock
type SafeMap struct {
    mu sync.RWMutex
    m  map[string]string
}

// sync.Map é adequado para:
// 1. Chaves são escritas uma vez mas lidas muitas vezes (ex: cache)
// 2. Múltiplas goroutines leem/escrevem chaves diferentes (sem sobreposição)

P3: Quando objetos no Pool são coletados pelo garbage collector?

Objetos no Pool podem ser limpos durante qualquer ciclo de GC. Não use Pool como armazenamento de longo prazo:

GO
// ❌ Uso errado: usando Pool como cache
pool := &sync.Pool{New: func() interface{} { return expensiveObject() }}
obj := pool.Get()
// ... usa
pool.Put(obj)
// Após o próximo GC, obj pode ter sido coletado

// ✅ Uso correto: reutiliza objetos temporários, reduz alocações
pool := &sync.Pool{
    New: func() interface{} {
        return make([]byte, 0, 4096) // pré-aloca buffer
    },
}
buf := pool.Get().([]byte)[:0] // obtém e reseta
// ... usa buf
pool.Put(buf) // retorna

P4: Como evitar deadlocks?

GO
// ❌ Deadlock: mesma goroutine trava duas vezes
var mu sync.Mutex
mu.Lock()
mu.Lock() // Bloqueia para sempre! Deadlock!

// ✅ Solução 1: Use defer para garantir liberação
mu.Lock()
defer mu.Unlock()
// ... libera mesmo em caso de panic

// ✅ Solução 2: Preste atenção na ordem dos locks, evite locks cruzados
// Duas goroutines: uma mantém lock A esperando por B, a outra mantém B esperando por A -> deadlock
// Solução: sempre bloqueie na ordem A->B

// ✅ Solução 3: Use locks com timeout (Go 1.18+ recomenda usar context)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// Use channel com select para controle de timeout

📖 Resumo

Conceito Pontos-chave
sync.Mutex Exclusão mútua, apenas uma goroutine pode manter por vez
sync.RWMutex Lock de leitura-escrita, múltiplas leituras, escrita única; ideal para cenários com muita leitura
sync.Once Garante que função execute apenas uma vez; adequado para singletons/inicialização
sync.Pool Pool de objetos, reutiliza objetos temporários, reduz pressão do GC
sync.Map Mapa concorrente seguro; melhor que mapa com lock em cenários específicos
atomic Operações atômicas; solução de concorrência segura mais leve
-race Detector de corrida; deve ser habilitado durante desenvolvimento
Prevenção de deadlock defer Unlock, ordem consistente de locks, evite locks aninhados

Princípios de Seleção:

  1. Contador/flag simples → atomic
  2. Exclusão mútua geral → Mutex
  3. Muita leitura, pouca escrita → RWMutex
  4. Inicialização única → Once
  5. Reutilização de objetos → Pool
  6. Leitura/escrita concorrente de key-value → sync.Map

📝 Exercícios

Exercício 1: Implementar uma Pilha Thread-safe

Implemente uma pilha concorrente segura (LIFO) suportando operações Push, Pop e Size.

GO
package main

import (
    "errors"
    "fmt"
    "sync"
)

// ThreadSafeStack é uma pilha concorrente segura
type ThreadSafeStack struct {
    // Seu código:
    // - Escolha estrutura de dados apropriada
    // - Escolha primitiva de sincronização apropriada
}

// NewStack cria uma nova pilha
func NewStack() *ThreadSafeStack {
    // Seu código
    return nil
}

// Push empilha um valor na pilha
func (s *ThreadSafeStack) Push(val int) {
    // Seu código
}

// Pop desempilha um valor da pilha (retorna erro quando vazia)
func (s *ThreadSafeStack) Pop() (int, error) {
    // Seu código
    return 0, errors.New("pilha vazia")
}

// Size retorna o número de elementos na pilha
func (s *ThreadSafeStack) Size() int {
    // Seu código
    return 0
}

func main() {
    stack := NewStack()
    var wg sync.WaitGroup

    // Push concorrente
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func(val int) {
            defer wg.Done()
            stack.Push(val)
        }(i)
    }
    wg.Wait()

    fmt.Println("Tamanho da pilha:", stack.Size()) // Deve imprimir 100

    // Pop concorrente
    for i := 0; i < 50; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            if val, err := stack.Pop(); err == nil {
                fmt.Println("Desempilhado:", val)
            }
        }()
    }
    wg.Wait()

    fmt.Println("Tamanho restante:", stack.Size()) // Deve imprimir 50
}

Exercício 2: Implementar Cache com TTL

Use sync.RWMutex para implementar um cache com suporte a TTL (time-to-live):

GO
package main

import (
    "fmt"
    "sync"
    "time"
)

// TTLCache é um cache com tempo de expiração
type TTLCache struct {
    // Seu código:
    // - Estrutura de armazenamento
    // - Lock de sincronização
    // - Configuração de TTL
}

// NewTTLCache cria um cache; ttl é o tempo de expiração
func NewTTLCache(ttl time.Duration) *TTLCache {
    // Seu código
    return nil
}

// Set define um par key-value
func (c *TTLCache) Set(key string, value interface{}) {
    // Seu código
}

// Get recupera um valor (retorna false se expirado)
func (c *TTLCache) Get(key string) (interface{}, bool) {
    // Seu código
    return nil, false
}

// Delete exclui uma chave
func (c *TTLCache) Delete(key string) {
    // Seu código
}

// Size retorna o número de pares key-value válidos
func (c *TTLCache) Size() int {
    // Seu código
    return 0
}

func main() {
    cache := NewTTLCache(2 * time.Second)

    cache.Set("name", "Linguagem Go")
    cache.Set("version", "1.21")

    if val, ok := cache.Get("name"); ok {
        fmt.Println("name =", val)
    }

    fmt.Println("Tamanho do cache:", cache.Size())

    time.Sleep(3 * time.Second)

    if _, ok := cache.Get("name"); !ok {
        fmt.Println("name expirou")
    }

    fmt.Println("Tamanho após expiração:", cache.Size())
}

Exercício 3: Map-Reduce Paralelo

Use o pacote sync para implementar um Map-Reduce paralelo simples:

GO
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

// ParallelMapReduce processa dados em paralelo e agrega resultados
// Parâmetros:
//   - data: slice de dados de entrada
//   - mapFn: função de mapeamento, converte entrada em um valor
//   - reduceFn: função de redução, mescla dois resultados
//
// Requisitos:
//   - Divida dados em segmentos, cada um processado por uma goroutine
//   - Use sync.WaitGroup para esperar todas as goroutines completarem
//   - Use sync.Mutex para proteger o resultado da redução
//   - Use atomic para contar total de elementos processados
func ParallelMapReduce(
    data []int,
    mapFn func(int) int,
    reduceFn func(int, int) int,
) int {
    // Seu código:
    // 1. Determine contagem de segmentos (sugere 4 segmentos)
    // 2. Lance goroutines para processar cada segmento
    // 3. mapFn processa cada elemento
    // 4. reduceFn mescla resultados dos segmentos
    // 5. Retorne resultado final
    return 0
}

func main() {
    data := make([]int, 100)
    for i := range data {
        data[i] = i + 1 // 1 a 100
    }

    // Computa a soma dos quadrados de todos os elementos
    result := ParallelMapReduce(
        data,
        func(x int) int { return x * x }, // map: quadrado
        func(a, b int) int { return a + b }, // reduce: soma
    )

    fmt.Println("1² + 2² + 3² + ... + 100² =", result)
    // Saída esperada: 338350
}

Próxima Lição

Tendo completado o pacote sync, você dominou as ferramentas centrais da programação concorrente em Go. Em seguida, consolidaremos o que aprendemos através de exercícios abrangentes.

👉 Lição 17: Exercícios Práticos de Concorrência

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%