Understanding goroutines synchronization

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

Understanding goroutines synchronization

问题

我正在尝试理解 golang 中的 channelssynchronization
当我使用 race detector 运行我的程序时,会检测到数据竞争。

我的程序:

func main() {
	ch := make(chan int)
	done := make(chan struct{})
	wg := sync.WaitGroup{}

	go func() {
		defer close(ch)
		defer close(done)
		wg.Wait()
		done <- struct{}{}
	}()

	for i := 0; i < 5; i++ {
		x := i
		wg.Add(1)
		go func() {
			defer wg.Done()
			fmt.Println("Value: ", x)
			ch <- x
		}()
	}
	
loop:
	for {
		select {
		case i := <-ch:
			fmt.Println("Value: ", i)
		case <-done:
			break loop
		}
	}
}

Race detector 报告:

==================
WARNING: DATA RACE
Write at 0x00c000020148 by goroutine 7:
  internal/race.Write()
      /home/linuxbrew/.linuxbrew/Cellar/go/1.16.5/libexec/src/internal/race/race.go:41 +0x125
  sync.(*WaitGroup).Wait()
      /home/linuxbrew/.linuxbrew/Cellar/go/1.16.5/libexec/src/sync/waitgroup.go:128 +0x126
  main.main.func1()
      /home/reddy/code/github.com/awesomeProject/prod.go:106 +0xc4

Previous read at 0x00c000020148 by main goroutine:
  internal/race.Read()
      /home/linuxbrew/.linuxbrew/Cellar/go/1.16.5/libexec/src/internal/race/race.go:37 +0x206
  sync.(*WaitGroup).Add()
      /home/linuxbrew/.linuxbrew/Cellar/go/1.16.5/libexec/src/sync/waitgroup.go:71 +0x219
  main.main()
      /home/reddy/code/github.com/awesomeProject/prod.go:112 +0x124

Goroutine 7 (running) created at:
  main.main()
      /home/reddy/code/github.com/awesomeProject/prod.go:103 +0x104
==================

我无法弄清楚出了什么问题。

我的分析:

  1. wg.Add(1) 增加了计数器。
  2. wg.Done() 在 goroutine 的末尾调用,减少计数器。
  3. ch <- x 这应该是一个阻塞调用,因为它是非缓冲通道。
  4. 循环应该在 done 通道有消息时迭代,这发生在 waitgroup 计数器变为零时,即所有 5 个 goroutine 都发布了消息。
  5. 一旦计数器变为零,wg goroutine 将恢复执行,并调用 done,一旦在主循环中消耗了消息,它就会中断循环,并且应该正常退出。
英文:

I am trying to understand the golang channels and synchronization.
When I run my program with race detector, it results in race detection.

My program:

func main() {
	ch := make(chan int)
	done := make(chan struct{})
	wg := sync.WaitGroup{}

	go func() {
		defer close(ch)
		defer close(done)
		wg.Wait()
		done &lt;- struct{}{}
	}()

	for i := 0; i &lt; 5; i++ {
		x := i
		wg.Add(1)
		go func() {
			defer wg.Done()
			fmt.Println(&quot;Value: &quot;, x)
			ch &lt;- x
		}()
	}
	
loop:
	for {
		select {
		case i := &lt;-ch:
			fmt.Println(&quot;Value: &quot;, i)
		case &lt;- done:
			break loop
		}
	}
}

Race detector report:

==================
WARNING: DATA RACE
Write at 0x00c000020148 by goroutine 7:
  internal/race.Write()
      /home/linuxbrew/.linuxbrew/Cellar/go/1.16.5/libexec/src/internal/race/race.go:41 +0x125
  sync.(*WaitGroup).Wait()
      /home/linuxbrew/.linuxbrew/Cellar/go/1.16.5/libexec/src/sync/waitgroup.go:128 +0x126
  main.main.func1()
      /home/reddy/code/github.com/awesomeProject/prod.go:106 +0xc4

Previous read at 0x00c000020148 by main goroutine:
  internal/race.Read()
      /home/linuxbrew/.linuxbrew/Cellar/go/1.16.5/libexec/src/internal/race/race.go:37 +0x206
  sync.(*WaitGroup).Add()
      /home/linuxbrew/.linuxbrew/Cellar/go/1.16.5/libexec/src/sync/waitgroup.go:71 +0x219
  main.main()
      /home/reddy/code/github.com/awesomeProject/prod.go:112 +0x124

Goroutine 7 (running) created at:
  main.main()
      /home/reddy/code/github.com/awesomeProject/prod.go:103 +0x104
==================

I am not able to figure out what's going wrong here.

My analysis:

  1. wg.Add(1) is incrementing the counter
  2. wg.Done() is called at the end of goroutine which decrements the counter
  3. ch &lt;- x this should be a blocking call as it's non buffered channel
  4. loop should iterate till done channel has some message which happens when the waitgroup counter goes to zero, i.e. all the 5 goroutines published the message
  5. once the counter goes to zero, wg goroutine will resume and done is called and once the message is consumed in the main loop, it breaks the loop and should gracefully exit.

答案1

得分: 6

程序在调用wg.Add和调用wg.Wait之间存在竞争。这些调用可以以任何顺序发生。当在调用wg.Add之前调用wg.Wait时,调用wg.Wait不会等待任何goroutine。

通过将调用wg.Add的位置移动到启动调用wg.Wait的goroutine之前来修复。这个改变确保了调用wg.Add发生在调用wg.Wait之前。

for i := 0; i < 5; i++ {
    x := i
    wg.Add(1)
    go func() {
        defer wg.Done()
        fmt.Println("Value: ", x)
        ch <- x
    }()
}

go func() {
    defer close(ch)
    defer close(done)
    wg.Wait()
    done <- struct{}{}
}()

当在竞争检测器下运行时,WaitGroup类型的代码会检查这个错误(模拟读取模拟写入)。

通过在主goroutine中当ch关闭时跳出循环来简化代码。不需要done通道。

ch := make(chan int)
wg := sync.WaitGroup{}

for i := 0; i < 5; i++ {
    x := i
    wg.Add(1)
    go func() {
        defer wg.Done()
        fmt.Println("Value: ", x)
        ch <- x
    }()
}

go func() {
    wg.Wait()
    close(ch)
}()

for i := range ch {
    fmt.Println("Value: ", i)
}
英文:

The program has a race between the calls to wg.Add and the call to wg.Wait. These calls can happen in any order. The call to wg.Wait does not wait for any of the goroutines when wg.Wait is called before the calls to wg.Add.

Fix by moving the calls to wg.Add before starting the goroutine that calls wg.Wait. This change ensures that the calls to wg.Add happen before the call to wg.Wait.

for i := 0; i &lt; 5; i++ {
    x := i
    wg.Add(1)
    go func() {
        defer wg.Done()
        fmt.Println(&quot;Value: &quot;, x)
        ch &lt;- x
    }()
}

go func() {
    defer close(ch)
    defer close(done)
    wg.Wait()
    done &lt;- struct{}{}
}()

The WaitGroup type has code to check for this error when running under the race detector (modeled read, modeled write).

Simplify the code by breaking out of the loop in the main goroutine when ch is closed. The done channel is not needed.

ch := make(chan int)
wg := sync.WaitGroup{}

for i := 0; i &lt; 5; i++ {
	x := i
	wg.Add(1)
	go func() {
		defer wg.Done()
		fmt.Println(&quot;Value: &quot;, x)
		ch &lt;- x
	}()
}

go func() {
	wg.Wait()
	close(ch)
}()

for i := range ch {
	fmt.Println(&quot;Value: &quot;, i)
}

huangapple
  • 本文由 发表于 2021年6月18日 12:42:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/68029427.html
匿名

发表评论

匿名网友

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

确定