【问题标题】:Understanding goroutines synchronization理解 goroutines 同步
【发布时间】:2021-09-02 20:28:06
【问题描述】:

我正在尝试理解 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
        }
    }
}

种族检测报告:

==================
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 &lt;- x 这应该是一个阻塞调用,因为它是非缓冲通道
  4. 循环应该迭代直到完成通道有一些消息当waitgroup计数器变为零时发生,即所有5个goroutine都发布了消息
  5. 一旦计数器归零,wg goroutine 将恢复并调用 done,一旦消息在主循环中被消耗,它会中断循环并正常退出。

【问题讨论】:

  • 第 112 行和第 106 行是什么?
  • @BurakSerdar wg.Add(1) -112 wg.Wait() -106
  • 直接来自sync's doc请注意,当计数器为零时调用[对Add] 的正增量必须发生在Wait 之前。具有负增量的调用或具有正增量的调用在计数器大于零时开始,可能随时发生。通常这意味着对 Add 的调用应该在创建 goroutine 的语句或其他要等待的事件之前执行。
  • 您的第一个例程在等待组完成之前关闭ch,因为您可能会在开始等待它的例程之后增加等待组。您也可以简单地关闭它,而不是进入完成的频道

标签: go channel goroutine waitgroup


【解决方案1】:

程序在对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 类型有代码在竞争检测器下运行时检查此错误(modeled readmodeled write)。

ch 关闭时,通过在主 goroutine 中中断循环来简化代码。不需要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)
}

【讨论】:

  • 这很有道理!
  • 感谢您指出现在完全有意义的建模阅读!
猜你喜欢
  • 1970-01-01
  • 2021-12-19
  • 2021-01-09
  • 1970-01-01
  • 2018-07-29
  • 2021-05-15
  • 1970-01-01
  • 2022-11-05
  • 1970-01-01
相关资源
最近更新 更多