How to correctly use sync.Cond?

前端 未结 8 798
忘了有多久
忘了有多久 2021-02-01 16:11

I\'m having trouble figuring out how to correctly use sync.Cond. From what I can tell, a race condition exists between locking the Locker and invoking the condition\'s Wait meth

相关标签:
8条回答
  • 2021-02-01 16:52

    Yes you can use one channel to pass Header to multiple Go routines.

    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 讨论(0)
  • 2021-02-01 16:58

    I finally discovered a way to do this and it doesn't involve sync.Cond at all - just the 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
    }
    

    How does this work?

    The mutex is locked at the beginning of the task, ensuring that anything calling WaitFor() will block. Once the headers are available and the mutex unlocked by the goroutine, each call to WaitFor() will execute one at a time. All future calls (even after the goroutine ends) will have no problem locking the mutex, since it will always be left unlocked.

    0 讨论(0)
  • 2021-02-01 16:58

    In the excellent book "Concurrency in Go" they provide the following easy solution while leveraging the fact that a channel that is closed will release all waiting clients.

    package main
    import (
        "fmt"
        "time"
    )
    func main() {
        httpHeaders := []string{}
        headerChan := make(chan interface{})
        var consumerFunc= func(id int, stream <-chan interface{}, funcHeaders *[]string)         
        {
            <-stream
            fmt.Println("Consumer ",id," got headers:", funcHeaders )   
        }
        for i:=0;i<3;i++ {
            go consumerFunc(i, headerChan, &httpHeaders)
        }
        fmt.Println("Getting headers...")
        time.Sleep(2*time.Second)
        httpHeaders=append(httpHeaders, "test1");
        fmt.Println("Publishing headers...")
        close(headerChan )
        time.Sleep(5*time.Second)
    }
    

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

    0 讨论(0)
  • 2021-02-01 17:00

    Looks like you c.Wait for Broadcast which would never happens with your time intervals. With

    time.Sleep(3 * time.Second) //Broadcast after any Wait for it
    c.Broadcast()
    

    your snippet seems to work http://play.golang.org/p/OE8aP4i6gY .Or am I missing something that you try to achive?

    0 讨论(0)
  • 2021-02-01 17:06
    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

    0 讨论(0)
  • 2021-02-01 17:13

    OP answered his own, but did not directly answer the original question, I am going to post how to correctly use sync.Cond.

    You do not really need sync.Cond if you have one goroutine for each write and read - a single sync.Mutex would suffice to communicate between them. sync.Cond could useful in situations where multiple readers wait for the shared resources to be available.

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

    Playground

    Having said that, using channels is still the recommended way to pass data around if the situation permitting.

    Note: sync.WaitGroup here is only used to wait for the goroutines to complete their executions.

    0 讨论(0)
提交回复
热议问题