Het werken met concurrerende collecties in Go is een belangrijk onderwerp geworden door de toenemende eisen aan multithreaded applicaties. Gewone maps in Go zijn niet thread-safe en kunnen leiden tot dataraces. De introductie van sync.Map bood een standaardoplossing voor veilige gedeelde toegang tot collecties zonder externe synchronisatie.
Achtergrond:
Voor de introductie van sync.Map moesten ontwikkelaars gewone maps gebruiken met een externe Mutex of RWMutex om veilige toegang vanuit meerdere goroutines te organiseren. Dit verhoogde de hoeveelheid code en de kans op synchronisatie-fouten. Sync.Map werd geïntroduceerd in Go 1.9 om het werken met concurrerende collecties te vereenvoudigen.
Probleem:
Gewone maps zijn niet thread-safe. Als meerdere goroutines tegelijkertijd lezen en schrijven naar een map zonder synchronisatie, kan dit leiden tot paniek of onverwachte resultaten. Mutex is moeilijk correct te gebruiken en kan leiden tot vergrendelingen en prestatieverlies. Ook is er complicatie met "double check" en werken met zwaar meetbare synchronisatie.
Oplossing:
sync.Map is een speciale structuur uit de standaardbibliotheek die thread-safe methoden biedt zoals Load, Store, LoadOrStore, Delete en Range. Het implementeert een (deeltijds) lock-free strategie, geoptimaliseerd voor scenario's met frequente leesoperaties en zeldzame schrijfacties.
Voorbeeldcode:
import ( "fmt" "sync" ) func main() { var m sync.Map m.Store("foo", 42) value, ok := m.Load("foo") fmt.Println(value, ok) // 42 true m.Delete("foo") }
Belangrijke kenmerken:
Kun je alle maps vervangen door sync.Map in multithreaded programma's?
Nee, sync.Map is geen universele vervanging voor gewone maps. Het is goed geschikt voor datatypes waarin concurrerende, onafhankelijke leesoperaties overheersen, maar bij intensieve schrijfacties (frequente modificaties) of voor kleine collecties zijn gewone maps + Mutex sneller en efficiënter.
Wat gebeurt er als je een normale map alleen voor lezen gebruikt in meerdere goroutines?
Als de map volledig is geïnitialiseerd en niet wordt gewijzigd na het starten van alle goroutines, is parallelle leestoegang toegestaan en veilig. Maar elke verwijdering of wijziging van gegevens zal leiden tot onvoorspelbaar gedrag, paniek of een corrupte map.
Welke datatypes kunnen worden gebruikt als sleutel voor sync.Map?
De regels zijn dezelfde als voor gewone maps: alleen vergelijkbare types. sync.Map accepteert echter sleutels van elk type interface{}, wat een risico kan vormen voor objecten met verschillende semantiek die niet met elkaar kunnen worden vergeleken of waarin runtime-fouten kunnen optreden.
Voorbeeldcode:
var m sync.Map m.Store([]int{1,2}, "value") // panic: runtime error: hash of unhashable type []int
Een ontwikkelaar gebruikte sync.Map om applicatie-instellingen op te slaan die zelden worden gewijzigd, maar vaak worden gelezen. Later begonnen ze echter massaal gegevens over gebruikerssessies te schrijven, wat leidde tot onverwachte verhoogde belasting van de GC en prestatieverlies.
Voordelen:
Nadelen:
Het team implementeerde sync.Map voor het opslaan van een cache van vaak aangevraagde rekenresultaten in een high-load service. Het aantal "lezingen" overschreed "schrijfacties" met honderden. Alles werkt stabiel en efficiënt, de code is korter en eenvoudiger te onderhouden.
Voordelen:
Nadelen: