如何确保 goroutine 在条件满足时被等待?

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

How to be 100% sure that goroutines are waited on condition?

问题

我正在阅读Katherine Cox-Buday的《Go并发编程》一书,并且书中有一个示例,展示了如何使用Condition.Broadcast来唤醒等待的goroutine。以下是书中的示例代码:

package main

import (
	"fmt"
	"sync"
)

type Button struct {
	Clicked *sync.Cond
}

func main() {
	button := &Button{Clicked: &sync.Cond{L: &sync.Mutex{}}}

	subscribe := func(c *sync.Cond, fn func()) {
		var goroutineRunning sync.WaitGroup
		goroutineRunning.Add(1)
		go func() {
			goroutineRunning.Done() //1
			c.L.Lock()
			// 2
			defer c.L.Unlock()
			c.Wait()
			fn()
		}()
		goroutineRunning.Wait()
	}

	var clickedRegistred sync.WaitGroup
	clickedRegistred.Add(2)

	subscribe(button.Clicked, func() {
		fmt.Println("Func #1")
		clickedRegistred.Done()
	})

	subscribe(button.Clicked, func() {
		fmt.Println("Func #2")
		clickedRegistred.Done()
	})

	button.Clicked.Broadcast()
	clickedRegistred.Wait()
}

这段代码是否百分之百地保证了并发安全?

是否可能出现这样的情况:主goroutine继续执行并在func goroutine被阻塞并等待条件信号/广播之前进行广播?

我的假设是:在//2的位置将//1的行移动,以避免这种情况。这样做是否有问题?或者说我描述的情况没有意义?

英文:

reading the book Concurrency in Go by Katherine Cox-Buday and there is example how to use Condition.Broadcast to wake up waiting goroutines. here is the example from the book

<!-- language: lang-go -->

package main

import (
	&quot;fmt&quot;
	&quot;sync&quot;
)

type Button struct {
	Clicked *sync.Cond
}

func main() {
	button := &amp;Button{Clicked: &amp;sync.Cond{L: &amp;sync.Mutex{}}}

	subscribe := func(c *sync.Cond, fn func()) {
		var goroutineRunning sync.WaitGroup
		goroutineRunning.Add(1)
		go func() {
			goroutineRunning.Done() //1
			c.L.Lock()
            // 2
			defer c.L.Unlock()
			c.Wait()
			fn()
		}()
		goroutineRunning.Wait()
	}

	var clickedRegistred sync.WaitGroup
	clickedRegistred.Add(2)

	subscribe(button.Clicked, func() {
		fmt.Println(&quot;Func #1&quot;)
		clickedRegistred.Done()
	})

	subscribe(button.Clicked, func() {
		fmt.Println(&quot;Func #2&quot;)
		clickedRegistred.Done()
	})

	button.Clicked.Broadcast()
	clickedRegistred.Wait()
}

is it code 100% concurrency safe?

is it possible situation when main goroutine continue the work and does broadcast before func goroutines parked and waited the signal/broadcast from condition?

I mean the situation when func goroutines done their work at line //1 but did not acquire the condition lock yet and main goroutine do broadcast this time, is it possible?

my assumption the follow: should the line //1 be moved at place //2 to avoid this situation?

or there is no problem and the situation I discribed does not make sence?

答案1

得分: 1

你的问题有两个部分:

  1. 这段代码是否存在潜在的竞态条件漏洞?是的:可以参考 https://stackoverflow.com/q/58368947/1256452

  2. Done调用移动是否是一个修复方法?在这个特定的代码中,是的,但我认为这可能不是一个特别好的修复方法。正如Andy Schweig在评论中指出,使用条件的Wait方法的代码更典型的做法是循环等待:

     for some_boolean { c.Wait() }
    

    调用c.Signalc.Broadcast的代码在调用SignalBroadcast之前会适当地设置布尔值(在这里设置为false),以指示等待者现在应该停止等待。

英文:

Your question has two parts, I think:

  1. Does this code have a potential race bug? Yes: see, e.g., https://stackoverflow.com/q/58368947/1256452

  2. Is moving the Done call a fix? In this particular code, yes, but I would say, perhaps not a particularly good fix. As Andy Schweig notes in a comment on his own answer, it's more typical for code that uses the condition's Wait method to loop:

    for some_boolean { c.Wait() }
    

    The code that calls c.Signal or c.Broadcast would set the boolean appropriately (here, to false) before calling Signal or Broadcast, to indicate that the waiter should now stop waiting.

huangapple
  • 本文由 发表于 2021年6月14日 19:58:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/67969827.html
匿名

发表评论

匿名网友

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

确定