105 votes

Comment utiliser RWMutex?

type Stat struct {
    counters     map[string]*int64
    countersLock sync.RWMutex
    averages     map[string]*int64
    averagesLock sync.RWMutex
}

Il est appelé ci-dessous

func (s *Stat) Count(name string) {
    s.countersLock.RLock()
    counter := s.counters[name]
    s.countersLock.RUnlock()
    if counter != nil {
        atomic.AddInt64(counter, int64(1))
        return
    }
}

Ma compréhension est que nous verrouillons d'abord le receveur s (qui est de type Stat) et ensuite nous ajoutons si le compteur existe.

Questions :

Q1: pourquoi avons-nous besoin de le verrouiller ? Que signifie RWMutex ?

Q2: s.countersLock.RLock() - cela verrouille-t-il l'ensemble du receveur s ou seulement le champ counters dans le type Stat ?

Q3: s.countersLock.RLock() - cela verrouille-t-il le champ averages ?

Q4: Pourquoi devrions-nous utiliser RWMutex ? Je pensais que le canal était la méthode préférée pour gérer la concurrence en Golang ?

Q5: Qu'est-ce que ce atomic.AddInt64 ? Pourquoi avons-nous besoin d'atomic dans ce cas ?

Q6: Pourquoi devrions-nous déverrouiller juste avant d'ajouter ?

50voto

creack Points 11635

Questions:

Q1: why do we need to lock it? What does RWMutex even mean?

RW représente Read/Write. Doc CF : http://golang.org/pkg/sync/#RWMutex.

Nous devons le verrouiller pour empêcher d'autres routines/threads de modifier la valeur pendant que nous la traitons.

Q2: s.countersLock.RLock() - est-ce que cela verrouille l'ensemble du receveur s ou uniquement le champ counters dans le type Stat ?

En tant que mutex, le verrouillage se produit uniquement lorsque vous appelez la fonction RLock(). Si une autre goroutine a déjà appelé la fonction WLock(), alors elle se bloque. Vous pouvez appeler un nombre quelconque de RLock() dans la même goroutine, il ne verrouillera pas.

Donc cela ne verrouille pas d'autres champs, même pas s.counters. Dans votre exemple, vous verrouillez la recherche dans la map pour trouver le compteur correct.

Q3: s.countersLock.RLock() - est-ce que cela verrouille le champ averages ?

Non, comme mentionné dans la question 2, un RLock ne verrouille que lui-même.

Q4: Pourquoi devrions-nous utiliser RWMutex? Je pensais que le canal était la manière préférée de gérer la concurrence en Golang?

Le canal est très utile mais parfois il n'est pas suffisant et parfois cela n'a pas de sens.

Ici, comme vous verrouillez l'accès à la map, un mutex a du sens. Avec un canal, vous auriez à avoir un canal tamponné de 1, envoyer avant et recevoir après. Pas très intuitif.

Q5: Qu'est-ce que ce atomic.AddInt64. Pourquoi avons-nous besoin d'atomic dans ce cas?

Cette fonction va incrémenter la variable donnée de manière atomique. Dans votre cas, vous avez une condition de course : counter est un pointeur et la variable réelle peut être détruite après la libération du verrou et avant l'appel à atomic.AddInt64. Si vous n'êtes pas familier avec ce type de choses, je vous conseillerais de rester avec les mutexes et d'effectuer tous les traitements nécessaires entre le verrouillage et le déverrouillage.

Q6: Pourquoi devrions-nous déverrouiller juste avant d'ajouter quelque chose?

Vous ne devriez pas.

Je ne sais pas ce que vous essayez de faire, mais voici un exemple (simple) : https://play.golang.org/p/cVFPB-05dw

12voto

tothemario Points 1041

Comparons cela au sync.Mutex classique, où seul un consommateur peut détenir le verrou à un moment donné. Et utilisons une analogie amusante : imaginez un grand milkshake à la fraise délicieux, qui doit être partagé par un groupe d'amis (consommateurs).

Les amis veulent partager le milkshake et décident d'utiliser une paille exclusive unique (le verrou), donc seul un ami peut boire à la paille à un moment donné. Un ami qui appelle m.Lock() signale qu'il veut boire. S'il n'y a personne qui boit, il peut continuer, mais si quelqu'un d'autre avait déjà utilisé la paille, il doit attendre (bloquer) jusqu'à ce que l'ami précédent ait fini de boire et appelle m.Unlock() de son côté.

\\  |  |
 \\ |__|

m.Lock()
m.Unlock()

Passons maintenant au sync.RWMutex (Read Write Mutex), où un nombre quelconque de lecteurs peuvent détenir le verrou, ou un seul écrivain peut détenir le verrou.

Sur l'analogie du milkshake à la fraise, les amis décident de partager le milkshake avec de nombreuses pailles "lecteur", et une seule paille exclusive "écrivain". Un ami appelant m.RLock() signale qu'il veut boire avec l'une des pailles "lecteur", et peut commencer à boire en même temps que d'autres lecteurs. Cependant, la paille exclusive "écrivain" fonctionne comme précédemment. Lorsque quelqu'un appelle m.Lock(), il signale qu'il veut boire seul. À ce moment-là, tout le monde est bloqué jusqu'à ce que toutes les pailles "lecteur" aient fini de boire (en appelant m.RUnlock()). Ensuite, l'écrivain exclusif commence à boire seul. Toute autre appel à m.RLock() ou m.Lock() doit attendre que l'ami avec la paille exclusive "écrivain" ait fini de boire (jusqu'à ce qu'il appelle m.Unlock()).

\\  |  |   //  //  //  //
 \\ |__|  //  //  //  //  ...

m.Lock()         m.RLock()
m.Unlock()       m.RUnlock()

La terminologie "lecteur" et "écrivain" est utilisée car c'est le scénario le plus courant. Les lectures concurrentes en mémoire sont acceptables, mais les écritures doivent être séquentielles. Si un processus essaie de lire une adresse mémoire tandis qu'un autre processus est en train d'écrire, cela pourrait provoquer une corruption de la mémoire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X