web-dev-qa-db-fra.com

Dans quelle mesure les cartes Golang sont-elles sécurisées pour les opérations de lecture/écriture simultanées?

Selon le blog Go,

Les cartes ne sont pas sûres pour une utilisation simultanée: ce qui se passe lorsque vous leur lisez et leur écrivez simultanément n'est pas défini. Si vous avez besoin de lire et d'écrire sur une carte à partir de goroutines exécutant simultanément, les accès doivent être transmis via un type de mécanisme de synchronisation . (source: https://blog.golang.org/go-maps-in-action )

Quelqu'un peut-il en dire plus? Les opérations de lecture simultanées semblent autorisées pour toutes les routines, mais les opérations de lecture/écriture simultanées peuvent générer une condition de concurrence critique si vous essayez de lire et d'écrire sur la même clé.

Ce dernier risque peut-il être réduit dans certains cas? Par exemple: 

  • La fonction A génère k et définit m [k] = 0. C’est la seule fois où A écrit pour mapper m. k est connu pour ne pas être en m.
  • A passe k à la fonction B s'exécutant simultanément
  • A lit alors m [k]. Si m [k] == 0, il attend, ne continuant que lorsque m [k]! = 0
  • B cherche k sur la carte. S'il le trouve, B définit m [k] sur un entier positif. Si ce n'est pas le cas, attend que k soit dans m.

Ce n'est pas du code (évidemment) mais je pense que cela montre les grandes lignes d'un cas où même si A et B tentent tous deux d'accéder à m, il n'y aura pas de condition de concurrence critique, contraintes supplémentaires.

22
John D.

Avant Golang 1.6, la lecture simultanée est correcte, l'écriture simultanée n'est pas correcte, mais l'écriture et la lecture simultanée sont correctes. Depuis Golang 1.6, la carte ne peut pas être lue lorsqu’elle est en cours d’écriture ... Donc, après Golang 1.6, la carte d’accès simultané devrait ressembler à:

package main

import (
    "sync"
    "time"
)

var m = map[string]int{"a": 1}
var lock = sync.RWMutex{}

func main() {
    go Read()
    time.Sleep(1 * time.Second)
    go Write()
    time.Sleep(1 * time.Minute)
}

func Read() {
    for {
        read()
    }
}

func Write() {
    for {
        write()
    }
}

func read() {
    lock.RLock()
    defer lock.RUnlock()
    _ = m["a"]
}

func write() {
    lock.Lock()
    defer lock.Unlock()
    m["b"] = 2
}

Ou vous obtiendrez l'erreur ci-dessous:  enter image description here

AJOUTÉE:

Vous pouvez détecter la course en utilisant go run -race race.go

Changer la fonction read:

func read() {
    // lock.RLock()
    // defer lock.RUnlock()
    _ = m["a"]
}

 enter image description here

Un autre choix:

Comme nous le savions, map a été implémenté par des compartiments et sync.RWMutex verrouille tous les compartiments. concurrent-map utilise fnv32 pour partager la clé et chaque compartiment utilise un sync.RWMutex

33
Bryce

La lecture simultanée (lecture seule) est correcte. L'écriture et/ou la lecture simultanée n'est pas correcte.

Plusieurs goroutines peuvent uniquement écrire et/ou lire la même carte si l’accès est synchronisé, par exemple. via le paquet sync , avec des canaux ou par d’autres moyens.

Votre exemple:

  1. La fonction A génère k et définit m [k] = 0. C’est la seule fois où A écrit pour mapper m. k est connu pour ne pas être en m.
  2. A passe k à la fonction B s'exécutant simultanément
  3. A lit alors m [k]. Si m [k] == 0, il attend, ne continuant que lorsque m [k]! = 0
  4. B cherche k sur la carte. S'il le trouve, B définit m [k] sur un entier positif. Si ce n'est pas le cas, attend que k soit dans m.

Votre exemple comporte 2 goroutines: A et B, et A tente de lire m (à l'étape 3) et B tente de l'écrire (à l'étape 4) simultanément. Il n'y a pas de synchronisation (vous n'en avez mentionné aucune), donc cela seul n'est pas autorisé/non déterminé.

Qu'est-ce que ça veut dire? Non déterminé signifie que même si B écrit m, A ne peut jamais observer le changement. Ou A peut observer un changement qui ne s'est même pas produit. Ou une panique peut survenir. Ou bien la Terre pourrait exploser à cause de cet accès simultané non synchronisé (bien que la probabilité de ce dernier cas soit extrêmement petite, voire inférieure à 1e-40).

Questions connexes:

Carte avec accès simultané

Qu'est-ce que ne pas être thread-safe signifie sur les cartes dans Go?

Quel est le danger de négliger goroutine/thread-safety lorsque vous utilisez une carte dans Go?

8
icza

Notes de version de Go 1.6

Le moteur d’exécution a ajouté une détection légère, au meilleur des efforts, du concurrent simultané mauvaise utilisation des cartes. Comme toujours, si un goroutine écrit sur une carte, non. L’autre goroutine devrait lire ou écrire la carte simultanément. Si le moteur d'exécution détecte cette condition, imprime un diagnostic et se bloque le programme. Le meilleur moyen d'en savoir plus sur le problème est de lancer le programme sous le détecteur de course, qui identifiera de manière plus fiable la course et donner plus de détails.

Les cartes sont des structures de données complexes, auto-réorganisées. Les accès simultanés en lecture et en écriture ne sont pas définis.

Sans code, il n'y a pas grand chose à dire. 

5
peterSO

Comme indiqué dans les autres réponses, le type natif map n'est pas goroutine- safe. Quelques notes après avoir lu les réponses actuelles:

  1. Ne pas utiliser différer pour déverrouiller, il a des frais généraux qui affectent les performances (voir this Nice post). Appelez déverrouiller directement.
  2. Vous pouvez obtenir de meilleures performances en réduisant le temps passé entre les verrous. Par exemple, en partageant la carte. 
  3. Il existe un paquetage commun (approchant 400 étoiles sur GitHub) utilisé pour résoudre ce problème appelé concurrent-maphere qui tient compte des performances et de la convivialité. Vous pouvez l'utiliser pour gérer les problèmes de concurrence pour vous. 
1
orcaman

Après de longues discussions, il a été décidé que l'utilisation typique des cartes ne nécessitait pas un accès sécurisé de plusieurs goroutines. Dans les cas où cela était le cas, la carte faisait probablement partie d'une structure de données plus grande ou d'un calcul déjà synchronisé. Par conséquent, exiger que toutes les opérations de carte utilisent un mutex ralentirait la plupart des programmes et augmenterait la sécurité de quelques-uns. Ce n’était cependant pas une décision facile, car cela signifiait que l’accès incontrôlé aux cartes pouvait bloquer le programme.

Le langage n'exclut pas les mises à jour de cartes atomiques. Lorsque cela est nécessaire, par exemple lors de l'hébergement d'un programme non approuvé, la mise en œuvre peut verrouiller l'accès aux cartes.

L'accès à la carte n'est dangereux que lorsque des mises à jour sont effectuées. Tant que toutes les goroutines ne font que lire - rechercher des éléments dans la carte, y compris en itérant à l'aide d'une boucle for range - et ne pas modifier la carte en attribuant des éléments ou en supprimant des éléments, il est prudent pour elles d'accéder simultanément à la carte sans synchronisation.

Pour aider à corriger l'utilisation de la carte, certaines implémentations de la langue contiennent une vérification spéciale qui signale automatiquement au moment de l'exécution qu'une carte est modifiée de manière non sécurisée par une exécution simultanée.

0
I.Tyger

Vous pouvez stocker un pointeur sur un int de la carte et faire en sorte que plusieurs goroutines lisent l’intérêt pointé pendant qu’un autre écrit une nouvelle valeur sur l’entier. La carte n'est pas mise à jour dans ce cas.

Ce ne serait pas idiomatique pour Go et pas ce que vous demandiez.

Ou au lieu de transmettre une clé à une carte, vous pouvez transmettre l'index à un tableau et le faire mettre à jour par un goroutine pendant que d'autres lisent l'emplacement.

Mais vous vous demandez probablement pourquoi la valeur d'une carte ne peut pas être mise à jour avec une nouvelle valeur alors que la clé est déjà dans la carte. Vraisemblablement, rien dans le schéma de hachage de la carte n'a été modifié - du moins, compte tenu de sa mise en œuvre actuelle. Il semblerait que les auteurs de Go ne veuillent pas prendre en compte de tels cas particuliers. Généralement, ils veulent que le code soit facile à lire et à comprendre, et une règle telle que ne pas autoriser l'écriture sur carte alors que d'autres goroutines sont en train de lire simplifie les choses. Désormais, ils peuvent même commencer à détecter les utilisations abusives au cours de l'exécution normale, permettant ainsi à de nombreuses personnes débogage.

0
WeakPointer