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.
Desafio
FácilVamos 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
Inventoryque armazena quantidades de produtos em um mapa e usa umsync.RWMutexpara proteger o acesso. Seu inventário deve suportar estas operações:NewInventory() *Inventory- Cria um novo inventário com um mapa inicializadoAddStock(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. Retornatruese for bem-sucedido,falsese houver estoque insuficiente (use bloqueio exclusivo)
Lembre-se de usar
deferpara 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, ouremove), 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-sucedido, ouInsufficient 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, ouremove) - Nome do produto
- Quantidade (apenas para as operações
adderemove)
- Tipo de 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 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
}
}
}
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 ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool