Travailler avec des collections concurrentes en Go est devenu un sujet important en raison des exigences croissantes des applications multithread. Les map ordinaires en Go ne sont pas sécurisées et peuvent entraîner des courses de données (data race). L'apparition de sync.Map a offert une solution standard pour un accès sûr aux collections sans synchronisation externe.
Contexte de la question :
Avant l'apparition de sync.Map, les développeurs devaient utiliser des map ordinaires avec un Mutex ou un RWMutex externe pour organiser un accès sûr depuis plusieurs goroutines. Cela augmentait la quantité de code et la probabilité d'erreurs de synchronisation. En Go 1.9, sync.Map a été introduit pour simplifier le travail avec des collections concurrentes.
Problème :
Une map ordinaire n'est pas sécurisée pour les threads. Si plusieurs goroutines lisent et écrivent dans une map sans synchronisation, cela peut entraîner des pannes ou des résultats inattendus. Le Mutex est difficile à utiliser correctement et peut entraîner des blocages et une dégradation des performances. Il existe également une complexité liée au "double check" et à la gestion de la synchronisation difficile à mesurer.
Solution :
sync.Map est une structure spéciale de la bibliothèque standard qui fournit des méthodes sécurisées pour les threads telles que Load, Store, LoadOrStore, Delete, Range. Elle implémente une stratégie sans verrou (partiellement), optimisée pour des scénarios comportant de nombreuses lectures et peu d'écritures.
Exemple de code :
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") }
Caractéristiques clés :
Peut-on remplacer toutes les map par sync.Map dans des programmes multithread ?
Non, sync.Map n'est pas un remplacement universel pour une map ordinaire. Elle est bien adaptée aux structures de données où les lectures concurrentes et indépendantes prédominent, mais en cas d'écritures intensives (modifications fréquentes) ou pour de petites collections, les map ordinaires + Mutex sont plus rapides et plus efficaces.
Que se passe-t-il si une map ordinaire est utilisée uniquement pour la lecture dans plusieurs goroutines ?
Si la map est complètement initialisée et n'est pas modifiée après le lancement de toutes les goroutines, la lecture parallèle est acceptable et sûre. Mais toute suppression ou modification des données entraînera un comportement imprévisible, des pannes ou une corrupted map.
Quels types de données peuvent être utilisés comme clé pour sync.Map ?
Les règles sont les mêmes que pour une map ordinaire : seulement des types comparables. Cependant, sync.Map accepte des clés de tout type d'interface{}, ce qui peut créer un risque d'objets avec une sémantique différente qui ne peuvent pas être comparés entre eux ou qui entraînent des erreurs d'exécution.
Exemple de code :
var m sync.Map m.Store([]int{1,2}, "value") // panic: runtime error: hash of unhashable type []int
Un développeur a utilisé sync.Map pour stocker les paramètres de l'application, qui changent rarement mais sont souvent lus. Cependant, plus tard, des données sur les sessions des utilisateurs ont commencé à y être massivement écrites, ce qui a entraîné une augmentation inattendue de la charge du GC et une dégradation des performances.
Avantages :
Inconvénients :
L'équipe a implémenté sync.Map pour stocker le cache des résultats de calculs souvent sollicités dans un service à forte charge. Le nombre de « lectures » dépasse le nombre « d'écritures » de plusieurs centaines de fois. Tout fonctionne de manière stable et efficace, le code est devenu plus court et plus facile à maintenir.
Avantages :
Inconvénients :