sync.Mutex e sync.RWMutex
Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 63 di 107.
Sebbene i canali siano il metodo preferito in Go per coordinare le goroutine, a volte è necessario proteggere direttamente i dati condivisi. Il pacchetto sync fornisce i mutex: blocchi che garantiscono che solo una goroutine alla volta acceda a una risorsa.
Un sync.Mutex ha due metodi: Lock() e Unlock(). Quando una goroutine chiama Lock(), ottiene l'accesso esclusivo. Le altre goroutine che chiamano Lock() rimarranno bloccate finché non viene chiamato 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
}Usare defer c.mu.Unlock() garantisce che il lock venga rilasciato anche se la funzione termina in anticipo o va in panico: un modello comune e consigliato.
Quando le letture sono frequenti ma le scritture rare, sync.RWMutex offre prestazioni migliori. Consente a più lettori di accedere contemporaneamente, ma agli scrittori concede l’accesso esclusivo:
type Cache struct {
mu sync.RWMutex
data map[string]string
}
func (c *Cache) Get(key string) string {
c.mu.RLock() // sono consentiti più lettori contemporaneamente
defer c.mu.RUnlock()
return c.data[key]
}
func (c *Cache) Set(key, value string) {
c.mu.Lock() // accesso esclusivo per la scrittura
defer c.mu.Unlock()
c.data[key] = value
}Usa RLock()/RUnlock() per le operazioni di lettura e Lock()/Unlock() per le scritture. Questo consente letture concorrenti garantendo al contempo la sicurezza delle scritture.
Sfida
FacileCostruiamo un sistema di inventario sicuro per l’uso concorrente che tiene traccia dei livelli di scorte dei prodotti. Il sistema gestirà in sicurezza letture e scritture concorrenti usando mutex, garantendo l’integrità dei dati quando più operazioni avvengono simultaneamente.
Organizzerai il codice in due file:
inventory.go: definisci il sistema di gestione dell’inventario sicuro per l’uso concorrente.Crea una struct
Inventoryche memorizza le quantità dei prodotti in una mappa e usa unsync.RWMutexper proteggere l’accesso. L’inventario deve supportare queste operazioni:NewInventory() *Inventory- Crea un nuovo inventario con una mappa inizializzataAddStock(product string, quantity int)- Aggiunge la quantità alle scorte di un prodotto (usa un blocco esclusivo perché modifica i dati)GetStock(product string) int- Restituisce le scorte attuali di un prodotto, o 0 se non viene trovato (usa un blocco di lettura perché legge soltanto i dati)RemoveStock(product string, quantity int) bool- Rimuove la quantità dalle scorte se sono sufficienti. Restituiscetruese l’operazione riesce,falsese le scorte sono insufficienti (usa un blocco esclusivo)
Ricorda di usare
deferper sbloccare, così da assicurarti che i blocchi vengano sempre rilasciati correttamente.main.go: leggi le operazioni e mostra il funzionamento del tuo inventario sicuro per l’uso concorrente.Leggi il numero di operazioni, poi elaborale una alla volta. Ogni operazione ha un tipo (
add,getoremove), il nome di un prodotto e, per le operazioniadd/remove, una quantità.Per ogni operazione, stampa il risultato:
add: stampaAdded [quantity] [product]get: stampa[product]: [stock] in stockremove: stampaRemoved [quantity] [product]se l’operazione riesce, oppureInsufficient stock for [product]in caso contrario
Verranno forniti i seguenti input:
- Riga 1: numero di operazioni (intero)
- Righe successive: per ogni operazione:
- Tipo di operazione (
add,getoremove) - Nome del prodotto
- Quantità (solo per le operazioni
adderemove)
- Tipo di operazione (
Ad esempio, dato:
5
add
apples
50
get
apples
remove
apples
30
remove
apples
25
get
applesL’output dovrebbe essere:
Added 50 apples
apples: 50 in stock
Removed 30 apples
Insufficient stock for apples
apples: 20 in stockRWMutex consente a più chiamate a GetStock di leggere contemporaneamente, mentre AddStock e RemoveStock ottengono l’accesso esclusivo quando modificano l’inventario.
Provalo tu
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leggi il numero di operazioni
line, _ := reader.ReadString('\n')
numOps, _ := strconv.Atoi(strings.TrimSpace(line))
// Crea un nuovo inventario
inventory := NewInventory()
// Elabora ogni operazione
for i := 0; i < numOps; i++ {
// Leggi il tipo di operazione
opLine, _ := reader.ReadString('\n')
opType := strings.TrimSpace(opLine)
// Leggi il nome del prodotto
productLine, _ := reader.ReadString('\n')
product := strings.TrimSpace(productLine)
// TODO: Gestisci ogni tipo di operazione (add, get, remove)
// Per "add" e "remove", leggi la quantità dall'input
// Chiama il metodo appropriato dell'inventario
// Stampa il risultato secondo la descrizione dell'esercizio
switch opType {
case "add":
// TODO: Leggi la quantità, aggiungi le scorte, stampa il risultato
case "get":
// TODO: Ottieni le scorte, stampa il risultato
case "remove":
// TODO: Leggi la quantità, rimuovi le scorte, stampa il risultato appropriato
}
}
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti di OOP in Go
File esterniWorkspace e moduli GoPacchetti e importazioniNomi esportati e non esportatiIntroduzione all’OOP in GoGli struct come classiDefinire metodi sugli structReceiver con puntatore vs receiver con valoreInizializzazione degli structFunzioni costruttriciRiepilogo - Calcolatrice semplice4Interfacce
Introduzione alle interfacceImplementazione implicitaL’interfaccia come contrattoInterfaccia vuota (any)Asserzione di tipoSwitch sul tipoComposizione di interfacceInterfacce Stringer ed ErrorRipasso - Calcolatore di forme7Incapsulamento
Campi esportati e non esportatiIncapsulamento a livello di packageMetodi Getter e SetterNascondimento delle informazioni in GoRiepilogo - Registri degli studenti10Tipi generici (Go 1.18+)
Introduzione ai tipi genericiParametri di tipoVincoli di tipoStruct genericheSoluzione alternativa per i metodi genericiRiepilogo - Collezione generica13Modelli di progettazione Parte 1
Introduzione ai modelli di progettazioneModello SingletonModello FactoryModello Abstract FactoryModello ObserverModello Strategy2Approfondimento su tipi e struct
Tipi di base e compostiDefinizioni di tipi personalizzatiTag delle structStruct anonimeStruct annidateValori zero e predefinitiRipasso - Rubrica5Composizione invece dell'ereditarietà
Perché Go non ha l'ereditarietàFondamenti dell'embedding delle structPromozione dei metodiEmbedding di più structEmbedding e aggregazione a confrontoMascheramento dei metodi embeddedRiepilogo: gerarchia dei dipendenti8Gestione degli errori e OOP
L'interfaccia errorTipi di errore personalizzatiWrapping degli errori (fmt.Errorf)Errori sentinellaerrors.Is() ed errors.As()Panic, Defer e RecoverRiepilogo - Parser di file11Libreria standard e OOP
io.Reader e io.Writersort.InterfaceInterfaccia fmt.Stringerencoding/json con le structInterfaccia http.HandlerRipasso - modelli REST API14Modelli di progettazione – Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeMiddleware come Decorator3Puntatori e memoria
Concetti base dei puntatori in GoPuntatori a structPassaggio per valore vs per riferimentoLa funzione new()Garbage collection in GoRiepilogo - Costruttore di liste concatenate6Polimorfismo in Go
Polimorfismo tramite interfacceDuck typing in GoRegole di soddisfacimento delle interfacceCollezioni polimorficheIniezione delle dipendenzeRiepilogo - Elaboratore di pagamenti9Concorrenza e OOP
Basi delle goroutineCanali e comunicazioneCanali bufferizzati vs non bufferizzatiIstruzione selectsync.Mutex e sync.RWMutexsync.WaitGroupProgettazione di struct thread-safeRiepilogo: worker poolEsercitati da solo: Compilatore Go online