3 votes

Mutex dans une boucle conduit à une sortie inattendue

J'ai ce bout de code simpliste (ou ici https://play.golang.org/p/KW8_OHUp9v )

package main

import (
    "fmt"
    "sync"
)

func main() {
    mutex := new(sync.Mutex)

    for i := 1; i < 5; i++ {
        for j := 1; j < 5; j++ {
            mutex.Lock()
            go func() {
                fmt.Printf("%d + %d = %d\n", i, j, j+i)
                mutex.Unlock()
            }()
        }
    }
}

Il produit un résultat comme celui-ci

1 + 2 = 3
1 + 3 = 4
1 + 4 = 5
2 + 5 = 7
2 + 2 = 4
2 + 3 = 5
2 + 4 = 6
3 + 5 = 8
3 + 2 = 5
3 + 3 = 6
3 + 4 = 7
4 + 5 = 9
4 + 2 = 6
4 + 3 = 7
4 + 4 = 8

Program exited.

En regardant les résultats, j'ai été surpris par plusieurs choses :

  1. Il n'y a pas de "1" pour le j
  2. Il y a des "5" pour les j
  3. Il n'y a que 3 valeurs pour i=1, au lieu de 4

Je peux comprendre l'absence de "1" car la variable est incrémentée avant d'être écrite.

Quelqu'un peut-il expliquer les points 2 et 3 ?

3voto

Adrian Points 2692

Vous fermez des variables dans une boucle, puis vous exécutez la fermeture dans un thread séparé, alors que ces variables continuent de changer. Lorsque vous faites cela, attendez-vous à l'inattendu - par exemple, vous voyez 5s parce que j est incrémenté à 5 lors de la dernière itération, ce qui entraîne la fin de la boucle, mais j contient toujours 5, que le fil séparé peut alors lire. Cela n'a rien à voir avec votre mutex ; c'est le partage des variables entre les threads. Si vous utilisez :

go func(i,j int) {
    fmt.Printf("%d + %d = %d\n", i, j, j+i)
    mutex.Unlock()
}(i,j)

Il transmet ensuite les valeurs de i y j au moment où votre goroutine est lancée, et les itérations suivantes ne l'affecteront pas : https://play.golang.org/p/P3kUP5e1Fp

1voto

Maciej Długoszek Points 312

Lorsque vous exécutez ce :

go func() {
                fmt.Printf("%d + %d = %d\n", i, j, j+i)
                mutex.Unlock()
            }()

La goroutine actuelle effectue une autre boucle qui incrémente j.

L'incrémentation a lieu avant printf, c'est pourquoi, même si la fonction a été appelée alors que
j était < 5 il pourrait être porté à 5 avant que la fonction n'ait le temps d'imprimer les valeurs.
En d'autres termes, le programme se déroule comme suit :

  • Entrer la boucle
  • Lock Mutex
  • Appeler func()
  • Incrément j
  • Imprimer les valeurs
  • Déverrouiller le Mutex

La solution serait de passer les valeurs par valeur à la fonction au lieu de les partager à travers les goroutines.

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