web-dev-qa-db-fra.com

Comment utiliser correctement sync.Cond?

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()
}

[ Run on the Go Playground ]

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.

13
Nathan Osman

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é.

1
Nathan Osman

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()
}

Cour de récréation

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.

7
garbagecollector

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
}

https://play.golang.org/p/O1r8v8yW6h

5
eric chiang

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?

2
Uvelichitel
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")
}

http://play.golang.org/p/fBBwoL7_pm

2
lofcek

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

2
Francesco Casula

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
    }
}
0
Jonas