Menu
Coddy logo textTech

sync.Mutex e sync.RWMutex

Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 63 de 107.

Embora os channels sejam a forma preferida do Go para coordenar goroutines, às vezes você precisa proteger dados compartilhados diretamente. O pacote sync fornece mutexes—bloqueios que garantem que apenas uma goroutine acesse um recurso por vez.

Um sync.Mutex possui dois métodos: Lock() e Unlock(). Quando uma goroutine chama Lock(), ela ganha acesso exclusivo. Outras goroutines que chamarem Lock() serão bloqueadas até que Unlock() seja chamado:

type Counter struct {
    mu    sync.Mutex
    value int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    c.value++
    c.mu.Unlock()
}

func (c *Counter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

O uso de defer c.mu.Unlock() garante que o lock seja liberado mesmo que a função retorne antecipadamente ou sofra um panic — um padrão comum e recomendado.

Quando as leituras são frequentes, mas as escritas são raras, o sync.RWMutex oferece melhor desempenho. Ele permite múltiplos leitores simultaneamente, mas os escritores obtêm acesso exclusivo:

type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Get(key string) string {
    c.mu.RLock()         // múltiplos leitores permitidos
    defer c.mu.RUnlock()
    return c.data[key]
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()          // acesso exclusivo para escrita
    defer c.mu.Unlock()
    c.data[key] = value
}

Use RLock()/RUnlock() para operações de leitura e Lock()/Unlock() para escritas. Isso permite leituras simultâneas, garantindo que as escritas sejam seguras.

challenge icon

Desafio

Fácil

Vamos construir um sistema de inventário thread-safe que rastreia os níveis de estoque de produtos. Seu sistema lidará com leituras e gravações simultâneas de forma segura usando mutexes, garantindo a integridade dos dados quando múltiplas operações ocorrem ao mesmo tempo.

Você organizará seu código em dois arquivos:

  • inventory.go: Defina seu sistema de gerenciamento de inventário thread-safe.

    Crie uma struct Inventory que armazena quantidades de produtos em um mapa e usa um sync.RWMutex para proteger o acesso. Seu inventário deve suportar estas operações:

    • NewInventory() *Inventory - Cria um novo inventário com um mapa inicializado
    • AddStock(product string, quantity int) - Adiciona quantidade ao estoque de um produto (use bloqueio exclusivo, pois isso modifica os dados)
    • GetStock(product string) int - Retorna o estoque atual de um produto, ou 0 se não for encontrado (use bloqueio de leitura, pois isso apenas lê os dados)
    • RemoveStock(product string, quantity int) bool - Remove a quantidade do estoque se houver estoque suficiente. Retorna true se for bem-sucedido, false se houver estoque insuficiente (use bloqueio exclusivo)

    Lembre-se de usar defer para desbloquear para garantir que os bloqueios sejam sempre liberados corretamente.

  • main.go: Leia as operações e demonstre seu inventário thread-safe.

    Leia o número de operações e, em seguida, processe cada operação. Cada operação tem um tipo (add, get, ou remove), um nome de produto e, para as operações add/remove, uma quantidade.

    Para cada operação, imprima o resultado:

    • add: Imprima Added [quantity] [product]
    • get: Imprima [product]: [stock] in stock
    • remove: Imprima Removed [quantity] [product] se for bem-sucedido, ou Insufficient stock for [product] se não for

As seguintes entradas serão fornecidas:

  • Linha 1: Número de operações (inteiro)
  • Linhas seguintes: Para cada operação:
    • Tipo de operação (add, get, ou remove)
    • Nome do produto
    • Quantidade (apenas para as operações add e remove)

Por exemplo, dado:

5
add
apples
50
get
apples
remove
apples
30
remove
apples
25
get
apples

Sua saída deve ser:

Added 50 apples
apples: 50 in stock
Removed 30 apples
Insufficient stock for apples
apples: 20 in stock

O RWMutex permite que múltiplas chamadas GetStock leiam simultaneamente, enquanto AddStock e RemoveStock obtêm acesso exclusivo ao modificar o inventário.

Folha de consulta

O pacote sync fornece mutexes para proteger dados compartilhados quando múltiplas goroutines precisam de acesso direto ao mesmo recurso.

sync.Mutex

Um sync.Mutex fornece acesso exclusivo usando Lock() e Unlock():

type Counter struct {
    mu    sync.Mutex
    value int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    c.value++
    c.mu.Unlock()
}

func (c *Counter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

O uso de defer com Unlock() garante que o bloqueio seja liberado mesmo se a função retornar antecipadamente ou entrar em pânico.

sync.RWMutex

Um sync.RWMutex permite múltiplos leitores simultâneos, mas acesso exclusivo para escritores:

type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Get(key string) string {
    c.mu.RLock()         // múltiplos leitores permitidos
    defer c.mu.RUnlock()
    return c.data[key]
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()          // acesso exclusivo para escrita
    defer c.mu.Unlock()
    c.data[key] = value
}

Use RLock()/RUnlock() para operações de leitura e Lock()/Unlock() para operações de escrita.

Experimente você mesmo

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Ler o número de operações
	line, _ := reader.ReadString('\n')
	numOps, _ := strconv.Atoi(strings.TrimSpace(line))
	
	// Criar um novo inventário
	inventory := NewInventory()
	
	// Processar cada operação
	for i := 0; i < numOps; i++ {
		// Ler o tipo de operação
		opLine, _ := reader.ReadString('\n')
		opType := strings.TrimSpace(opLine)
		
		// Ler o nome do produto
		productLine, _ := reader.ReadString('\n')
		product := strings.TrimSpace(productLine)
		
		// TODO: Lidar com cada tipo de operação (add, get, remove)
		// Para "add" e "remove", ler a quantidade da entrada
		// Chamar o método de inventário apropriado
		// Imprimir o resultado de acordo com a descrição do desafio
		
		switch opType {
		case "add":
			// TODO: Ler quantidade, adicionar estoque, imprimir resultado
			
		case "get":
			// TODO: Obter estoque, imprimir resultado
			
		case "remove":
			// TODO: Ler quantidade, remover estoque, imprimir resultado apropriado
			
		}
	}
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos