Je n'arrive pas à comprendre comment utiliser correctement sync.Cond
. D'après ce que je peux dire, une situation critique existe entre le verrouillage du casier et l'appel de la méthode Wait de la condition. Cet exemple ajoute un délai artificiel entre les deux lignes du goroutine principal pour simuler la situation de concurrence critique:
package main
import (
"sync"
"time"
)
func main() {
m := sync.Mutex{}
c := sync.NewCond(&m)
go func() {
time.Sleep(1 * time.Second)
c.Broadcast()
}()
m.Lock()
time.Sleep(2 * time.Second)
c.Wait()
}
Cela provoque une panique immédiate:
erreur fatale: toutes les goroutines sont endormies - deadlock! /usr/local/go/src/runtime/sema.go:241 + 0x2e0 sync. (* Cond) .Wait (0x10330200, 0x0) /usr/local/go/src/sync/cond.go:63 + 0xe0 main.main () /tmp/sandbox301865429/main.go:17 + 0x1a0
Qu'est-ce que je fais mal? Comment puis-je éviter cette condition de concurrence apparente? Existe-t-il une meilleure construction de synchronisation que je devrais utiliser?
Edit: Je réalise que j'aurais dû mieux expliquer le problème que je tente de résoudre ici. J'ai un goroutine de longue date qui télécharge un fichier volumineux et un certain nombre d'autres goroutines qui ont besoin d'accéder aux en-têtes HTTP lorsqu'ils sont disponibles. Ce problème est plus difficile qu'il n'y paraît.
Je ne peux pas utiliser de canaux car un seul goroutine recevrait alors la valeur. Et certains des autres goroutines essaieraient de récupérer les en-têtes longtemps après qu’ils soient déjà disponibles.
Le goroutine de téléchargeur pourrait simplement stocker les en-têtes HTTP dans une variable et utiliser un mutex pour en protéger l'accès. Cependant, cela ne permet pas aux autres goroutines "d'attendre" qu'elles soient disponibles.
J'avais pensé qu'un sync.Mutex
et un sync.Cond
ensemble pourraient permettre d'atteindre cet objectif, mais il semble que cela ne soit pas possible.
J'ai finalement découvert un moyen de faire cela et cela n'implique pas du tout sync.Cond
- juste le mutex.
type Task struct {
m sync.Mutex
headers http.Header
}
func NewTask() *Task {
t := &Task{}
t.m.Lock()
go func() {
defer t.m.Unlock()
// ...do stuff...
}()
return t
}
func (t *Task) WaitFor() http.Header {
t.m.Lock()
defer t.m.Unlock()
return t.headers
}
Comment cela marche-t-il?
Le mutex est verrouillé au début de la tâche, ce qui garantit que tout ce qui appelle WaitFor()
sera bloqué. Une fois que les en-têtes sont disponibles et que le mutex est déverrouillé par le goroutine, chaque appel à WaitFor()
sera exécuté un par un. Tous les futurs appels (même après la fin de la goroutine) n'auront aucun problème à verrouiller le mutex, car il sera toujours laissé déverrouillé.
OP a répondu à la sienne, mais n'a pas répondu directement à la question initiale, je vais vous expliquer comment utiliser correctement sync.Cond
.
Vous n'avez pas vraiment besoin de sync.Cond
si vous avez un goroutine pour chaque écriture et lecture - un seul sync.Mutex
suffira pour communiquer entre eux. sync.Cond
pourrait être utile lorsque plusieurs lecteurs attendent que les ressources partagées soient disponibles.
var sharedRsc = make(map[string]interface{})
func main() {
var wg sync.WaitGroup
wg.Add(2)
m := sync.Mutex{}
c := sync.NewCond(&m)
go func() {
// this go routine wait for changes to the sharedRsc
c.L.Lock()
for len(sharedRsc) == 0 {
c.Wait()
}
fmt.Println(sharedRsc["rsc1"])
c.L.Unlock()
wg.Done()
}()
go func() {
// this go routine wait for changes to the sharedRsc
c.L.Lock()
for len(sharedRsc) == 0 {
c.Wait()
}
fmt.Println(sharedRsc["rsc2"])
c.L.Unlock()
wg.Done()
}()
// this one writes changes to sharedRsc
c.L.Lock()
sharedRsc["rsc1"] = "foo"
sharedRsc["rsc2"] = "bar"
c.Broadcast()
c.L.Unlock()
wg.Wait()
}
Cela dit, l’utilisation de canaux est toujours le moyen recommandé de transmettre des données si la situation le permet.
Remarque: sync.WaitGroup
n’est utilisé ici que pour attendre que les goroutines aient terminé leurs exécutions.
Vous devez vous assurer que c.Broadcast s'appelle après votre appel à c.Wait. La version correcte de votre programme serait:
package main
import (
"fmt"
"sync"
)
func main() {
m := &sync.Mutex{}
c := sync.NewCond(m)
m.Lock()
go func() {
m.Lock() // Wait for c.Wait()
c.Broadcast()
m.Unlock()
}()
c.Wait() // Unlocks m
}
On dirait que vous attendez la diffusion, ce qui ne se produirait jamais avec vos intervalles de temps .
time.Sleep(3 * time.Second) //Broadcast after any Wait for it
c.Broadcast()
votre extrait semble fonctionner http://play.golang.org/p/OE8aP4i6gY . Est-ce que je manque quelque chose que vous essayez de réaliser?
package main
import (
"fmt"
"sync"
"time"
)
func main() {
m := sync.Mutex{}
m.Lock() // main gouroutine is owner of lock
c := sync.NewCond(&m)
go func() {
m.Lock() // obtain a lock
defer m.Unlock()
fmt.Println("3. goroutine is owner of lock")
time.Sleep(2 * time.Second) // long computing - because you are the owner, you can change state variable(s)
c.Broadcast() // State has been changed, publish it to waiting goroutines
fmt.Println("4. goroutine will release lock soon (deffered Unlock")
}()
fmt.Println("1. main goroutine is owner of lock")
time.Sleep(1 * time.Second) // initialization
fmt.Println("2. main goroutine is still lockek")
c.Wait() // Wait temporarily release a mutex during wating and give opportunity to other goroutines to change the state.
// Because you don't know, whether this is state, that you are waiting for, is usually called in loop.
m.Unlock()
fmt.Println("Done")
}
Voici un exemple pratique avec deux routines aller. Ils commencent les uns après les autres mais le second attend une condition qui est diffusée par le premier avant de procéder:
package main
import (
"sync"
"fmt"
"time"
)
func main() {
lock := sync.Mutex{}
lock.Lock()
cond := sync.NewCond(&lock)
waitGroup := sync.WaitGroup{}
waitGroup.Add(2)
go func() {
defer waitGroup.Done()
fmt.Println("First go routine has started and waits for 1 second before broadcasting condition")
time.Sleep(1 * time.Second)
fmt.Println("First go routine broadcasts condition")
cond.Broadcast()
}()
go func() {
defer waitGroup.Done()
fmt.Println("Second go routine has started and is waiting on condition")
cond.Wait()
fmt.Println("Second go routine unlocked by condition broadcast")
}()
fmt.Println("Main go routine starts waiting")
waitGroup.Wait()
fmt.Println("Main go routine ends")
}
La sortie peut varier légèrement car la deuxième routine aller peut commencer avant la première et vice-versa:
Main go routine starts waiting
Second go routine has started and is waiting on condition
First go routine has started and waits for 1 second before broadcasting condition
First go routine broadcasts condition
Second go routine unlocked by condition broadcast
Main go routine ends
https://Gist.github.com/fracasula/21565ea1cf0c15726ca38736031edc70
Oui, vous pouvez utiliser un canal pour transmettre l'en-tête à plusieurs routines Go.
headerChan := make(chan http.Header)
go func() { // This routine can be started many times
header := <-headerChan // Wait for header
// Do things with the header
}()
// Feed the header to all waiting go routines
for more := true; more; {
select {
case headerChan <- r.Header:
default: more = false
}
}