Die Arbeit mit konkurrierenden Sammlungen in Go ist aufgrund der steigenden Anforderungen an mehrfädige Anwendungen ein wichtiges Thema geworden. Normale Maps in Go sind nicht threadsicher und können zu Datenrennen führen. Die Einführung von sync.Map bot eine standardisierte Lösung für den sicheren gleichzeitigen Zugriff auf Sammlungen ohne externe Synchronisation.
Historie:
Vor der Einführung von sync.Map mussten Entwickler normale Maps mit externem Mutex oder RWMutex verwenden, um einen sicheren Zugriff von mehreren Goroutinen zu gewährleisten. Dies erhöhte den Codeaufwand und die Wahrscheinlichkeit von Synchronisationsfehlern. Mit Go 1.9 wurde sync.Map eingeführt, um die Arbeit mit konkurrierenden Sammlungen zu erleichtern.
Problem:
Eine normale Map ist nicht threadsicher. Wenn mehrere Goroutinen ohne Synchronisation in eine Map lesen und schreiben, führt dies zu Paniken oder unerwarteten Ergebnissen. Mutex ist schwierig richtig zu verwenden und kann zu Blockierungen und Leistungsverschlechterungen führen. Auch die Problematik der "doppelten Überprüfung" und die Arbeit mit schwer messbaren Synchronisationen entstehen.
Lösung:
sync.Map ist eine spezielle Struktur aus der Standardbibliothek, die threadsichere Methoden wie Load, Store, LoadOrStore, Delete und Range bereitstellt. Sie implementiert eine teilweise lock-free Strategie, die für Szenarien mit häufigen Lesevorgängen und seltenen Schreibvorgängen optimiert ist.
Beispielcode:
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") }
Schlüsselfunktionen:
Kann man alle Maps in mehrfädigen Programmen durch sync.Map ersetzen?
Nein, sync.Map ist kein universeller Ersatz für normale Maps. Sie eignet sich gut für Datenstrukturen, in denen konkurrierende, unabhängige Lesevorgänge dominieren, aber bei intensiven Schreibvorgängen (häufigen Modifikationen) oder kleinen Sammlungen sind normale Maps + Mutex schneller und effizienter.
Was passiert, wenn eine normale Map nur zum Lesen in mehreren Goroutinen verwendet wird?
Wenn die Map vollständig initialisiert ist und nach dem Start aller Goroutinen nicht mehr geändert wird, ist paralleles Lesen zulässig und sicher. Aber jede Löschung oder Änderung der Daten führt zu unvorhersehbarem Verhalten, Panik oder einer beschädigten Map.
Welche Datentypen können als Schlüssel für sync.Map verwendet werden?
Die Regeln sind die gleichen wie für normale Maps: nur vergleichbare Typen. sync.Map akzeptiert jedoch Schlüssel des beliebigen Typs interface{}, was das Risiko mit sich bringt, dass Objekte mit unterschiedlicher Semantik verwendet werden, die nicht miteinander verglichen werden können oder die zur Laufzeit Fehler verursachen können.
Beispielcode:
var m sync.Map m.Store([]int{1,2}, "value") // panic: runtime error: hash of unhashable type []int
Ein Entwickler verwendete sync.Map zur Speicherung der Anwendungseinstellungen, die selten geändert, aber häufig gelesen werden. Später begannen jedoch Daten über Benutzersitzungen massenhaft geschrieben zu werden, was zu einem unerwarteten Anstieg der GC-Last und einer Leistungsverschlechterung führte.
Vorteile:
Nachteile:
Das Team implementierte sync.Map zur Speicherung des Caches von häufig angeforderten Berechnungsergebnissen in einem Hochlastdienst. Die Anzahl der "Lesevorgänge" übersteigt die "Schreibvorgänge" um das Hundertfache. Alles funktioniert stabil und effizient, der Code ist kürzer und einfacher zu warten.
Vorteile:
Nachteile: