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.
Desafio
FácilVamos 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
Inventoryque armazene as quantidades dos produtos em um mapa e use umsync.RWMutexpara proteger o acesso. Seu inventário deve oferecer suporte às seguintes operações:NewInventory() *Inventory- Cria um novo inventário com um mapa inicializadoAddStock(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. Retornatruese for bem-sucedido,falsese o estoque for insuficiente (use um bloqueio exclusivo)
Lembre-se de usar
deferpara 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,getouremove), um nome de produto e, para as operaçõesadd/remove, uma quantidade.Para cada operação, imprima o resultado:
add: ImprimaAdded [quantity] [product]get: Imprima[product]: [stock] in stockremove: ImprimaRemoved [quantity] [product]se for bem-sucedida, ouInsufficient 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,getouremove) - Nome do produto
- Quantidade (somente para operações
adderemove)
- Tipo da operação (
Por exemplo, dado:
5
add
apples
50
get
apples
remove
apples
30
remove
apples
25
get
applesSua saída deve ser:
Added 50 apples
apples: 50 in stock
Removed 30 apples
Insufficient stock for apples
apples: 20 in stockO 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
}
}
}
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
1Fundamentos de OOP em Go
Arquivos ExternosWorkspace e Módulos do GoPacotes e ImportsNomes Exportados vs. Não ExportadosIntrodução à OOP em GoStructs como ClassesDefinindo Métodos em StructsReceptores de Ponteiro vs. de ValorInicialização de StructsFunções ConstrutorasRecapitulação — Calculadora Simples4Interfaces
Introdução às InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Asserção de TipoSwitch de TipoComposição de InterfacesInterfaces Stringer e ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs. Não ExportadosEncapsulamento no Nível de PacoteMétodos Getters e SettersOcultação de Informações em GoRecapitulação - Registros de Alunos10Genéricos (Go 1.18+)
Introdução aos genéricosParâmetros de tipoRestrições de tipoStructs genéricasAlternativa para métodos genéricosRecapitulação - Coleção genérica13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão Abstract FactoryPadrão ObserverPadrão Strategy2Tipos e Structs em Profundidade
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosTags de StructStructs AnônimasStructs AninhadasValores Zero e PadrõesRecapitulação - Livro de Contatos5Composição em vez de herança
Por que Go não tem herançaNoções básicas de incorporação de structsPromoção de métodosIncorporação de várias structsIncorporação vs agregaçãoOcultação de métodos incorporadosRecapitulação - Hierarquia de funcionários8Tratamento de Erros e OOP
A interface errorTipos de Erro PersonalizadosEncapsulamento de Erros (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - Analisador de Arquivos3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs. ReferênciaA Função new()Coleta de Lixo em GoRecapitulação - Construtor de Lista Ligada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfacesColeções PolimórficasInjeção de DependênciasRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e comunicaçãoCanais com e sem bufferInstrução selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de structs thread-safeRevisão - Worker PoolPratique por conta própria: Compilador de Go online