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 canais 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 tem dois métodos: Lock() e Unlock(). Quando uma goroutine chama Lock(), ela obtém acesso exclusivo. Outras goroutines que chamarem Lock() ficarã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
}

Usar defer c.mu.Unlock() garante que o bloqueio seja liberado mesmo que a função retorne antecipadamente ou entre em pânico: um padrão comum e recomendado.

Quando as leituras são frequentes, mas as escritas são raras, sync.RWMutex oferece melhor desempenho. Ele permite vários 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 gravações. Isso permite leituras simultâneas enquanto garante que as gravações sejam seguras.

challenge icon

Desafio

Fácil

Vamos criar um sistema de inventário seguro para uso com múltiplas threads que acompanha os níveis de estoque dos produtos. Seu sistema lidará com leituras e escritas concorrentes com segurança usando mutexes, garantindo a integridade dos dados quando várias operações ocorrerem simultaneamente.

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

  • inventory.go: Defina seu sistema de gerenciamento de inventário seguro para uso com múltiplas threads.

    Crie uma estrutura Inventory que armazene as quantidades dos produtos em um mapa e use um sync.RWMutex para proteger o acesso. Seu inventário deve oferecer suporte às seguintes operações:

    • NewInventory() *Inventory - Cria um novo inventário com um mapa inicializado
    • AddStock(product string, quantity int) - Adiciona a quantidade ao estoque de um produto (use um 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 um 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 o estoque for insuficiente (use um bloqueio exclusivo)

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

  • main.go: Leia as operações e demonstre seu inventário seguro para uso com múltiplas threads.

    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-sucedida, ou Insufficient stock for [product] caso contrário

As seguintes entradas serão fornecidas:

  • Linha 1: Número de operações (inteiro)
  • Linhas seguintes: Para cada operação:
    • Tipo da operação (add, get ou remove)
    • Nome do produto
    • Quantidade (somente para 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 várias chamadas de GetStock leiam simultaneamente, enquanto AddStock e RemoveStock obtêm acesso exclusivo ao modificar o inventário.

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 apropriado do inventário
		// Imprimir o resultado de acordo com a descrição do desafio
		
		switch opType {
		case "add":
			// TODO: Ler a quantidade, adicionar estoque, imprimir o resultado
			
		case "get":
			// TODO: Obter o estoque, imprimir o resultado
			
		case "remove":
			// TODO: Ler a quantidade, remover estoque, imprimir o 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

Pratique por conta própria: Compilador de Go online