Understanding sync.cond in golang

huangapple go评论85阅读模式
英文:

Understanding sync.cond in golang

问题

最近我开始学习Go语言,并遇到了sync.Cond函数。我对它的工作原理有一些困惑。

考虑下面的代码:

var sharedRsc = make(map[string]interface{})

func main() {
    var wg sync.WaitGroup
    mu := sync.Mutex{}
    c := sync.NewCond(&mu)

    wg.Add(1)
    go func() {
        defer wg.Done()

        c.L.Lock()
        // for len(sharedRsc) == 0 {
        c.Wait()
        // }

        c.L.Unlock()
        fmt.Println(sharedRsc["rsc1"])
    }()

    // writes changes to sharedRsc
    c.L.Lock()
    sharedRsc["rsc1"] = "foo"
    c.Signal()
    c.L.Unlock()

    wg.Wait()
}

当执行这段代码时,会导致死锁。我理解原因可能是在c.Signal之前调用了c.Wait。

但我不明白的是,当我们取消注释for循环时,它就能正常工作。那么这个for循环是如何解决这个竞态条件的呢?即使在这里,c.Signal在c.Wait之前仍有可能被调用,对吗?

英文:

I have started learning golang recently and I came across this sync.Cond function. I have little trouble understanding how this works.

consider the below code

var sharedRsc = make(map[string]interface{})

func main() {
	var wg sync.WaitGroup
	mu := sync.Mutex{}
	c := sync.NewCond(&mu)

	wg.Add(1)
	go func() {
		defer wg.Done()

		c.L.Lock()
		// for len(sharedRsc) == 0 {
		c.Wait()
		// }

		c.L.Unlock()
		fmt.Println(sharedRsc["rsc1"])
	}()

	// writes changes to sharedRsc
	c.L.Lock()
	sharedRsc["rsc1"] = "foo"
	c.Signal()
	c.L.Unlock()

	wg.Wait()
}

when this is executed it leads to a deadlock. I understand the reason might be c.Signal is called before c.Wait.

But what I don't understand is when we uncomment the for loop it works. So how does the for loop solves this race condition? Even here there is a possibility c.Signal can be called before c.Wait right?

答案1

得分: 1

你将需要使用for循环,因为如果在第二个等待协程之前发出信号,它将在Wait()处发生死锁,因为之后没有人会发出信号。

考虑以下执行流程:

  • 修改sharedRsc
  • c.Signal()
  • wg.Wait()
  • c.Wait()
英文:

You will need the for loop because if the cond is signaled before the second coroutine waiting, it will deadlock at the Wait() because no one will signal after that.

Consider the following execution flow:

  • Modify sharedRsc
  • c.Signal()
  • wg.Wait()
  • c.Wait()

答案2

得分: -1

这是正确使用sync.Cond的方法。

如果每个写入和读取操作都有一个goroutine,实际上不需要使用sync.Cond - 一个单独的sync.Mutex就足够在它们之间进行通信了。sync.Cond在多个读取器等待共享资源可用的情况下非常有用。

var sharedRsc = make(map[string]interface{})
func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    m := sync.Mutex{}
    c := sync.NewCond(&m)
    go func() {
        // 这个goroutine等待sharedRsc的变化
        c.L.Lock()
        for len(sharedRsc) == 0 {
            c.Wait()
        }
        fmt.Println(sharedRsc["rsc1"])
        c.L.Unlock()
        wg.Done()
    }()

    go func() {
        // 这个goroutine等待sharedRsc的变化
        c.L.Lock()
        for len(sharedRsc) == 0 {
            c.Wait()
        }
        fmt.Println(sharedRsc["rsc2"])
        c.L.Unlock()
        wg.Done()
    }()

    // 这个goroutine将变化写入sharedRsc
    c.L.Lock()
    sharedRsc["rsc1"] = "foo"
    sharedRsc["rsc2"] = "bar"
    c.Broadcast()
    c.L.Unlock()
    wg.Wait()
}

你可以在Go Playground中运行它。

如果情况允许,使用通道仍然是传递数据的推荐方式。

注意:这里的sync.WaitGroup仅用于等待goroutine完成执行。

为了更好地理解,请参考官方文档:https://pkg.go.dev/sync

还可以参考类似的Stack Overflow问题:https://stackoverflow.com/questions/36857167/how-to-correctly-use-sync-cond

英文:

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

You can it in Go Playground.

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.

For better understanding refer the official docs : https://pkg.go.dev/sync

And also see the similar SO : https://stackoverflow.com/questions/36857167/how-to-correctly-use-sync-cond

huangapple
  • 本文由 发表于 2022年1月18日 13:50:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/70750859.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定