英文:
Understanding goroutines synchronization
问题
我正在尝试理解 golang 中的 channels 和 synchronization。
当我使用 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
==================
我无法弄清楚出了什么问题。
我的分析:
wg.Add(1)
增加了计数器。wg.Done()
在 goroutine 的末尾调用,减少计数器。ch <- x
这应该是一个阻塞调用,因为它是非缓冲通道。- 循环应该在 done 通道有消息时迭代,这发生在
waitgroup
计数器变为零时,即所有 5 个 goroutine 都发布了消息。 - 一旦计数器变为零,
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 <- 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 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:
wg.Add(1)
is incrementing the counterwg.Done()
is called at the end of goroutine which decrements the counterch <- x
this should be a blocking call as it's non buffered channel- 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 - 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 < 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{}{}
}()
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 < 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)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论