go - Race 暂停一组 goroutines

标签 go synchronization channel

我有一堆 goroutines 在循环中做一些事情。我希望能够暂停所有这些,运行一些任意代码,然后恢复它们。我尝试这样做的方式可能不是惯用的(我希望有更好的解决方案),但我不明白为什么它不起作用。

精简到最基本的部分(底部的驱动程序代码):

type looper struct {
    pause  chan struct{}
    paused sync.WaitGroup
    resume chan struct{}
}

func (l *looper) loop() {
    for {
        select {
        case <-l.pause:
            l.paused.Done()
            <-l.resume
        default:
            dostuff()
        }
    }
}

func (l *looper) whilePaused(fn func()) {
    l.paused.Add(32)
    l.resume = make(chan struct{})
    close(l.pause)
    l.paused.Wait()
    fn()
    l.pause = make(chan struct{})
    close(l.resume)
}

我启动了 32 个 goroutines 都在运行 loop() , 然后调用 whilePaused连续 100 次,似乎一切正常……但如果我用 -race 运行它,它告诉我在 l.resume 上有一场比赛在 whilePaused 中写入它之间( l.resume = make(chan struct{}) ) 并在 loop 中阅读(<-l.resume)。

我不明白为什么会这样。根据The Go Memory Model , 那close(l.pause)应该在 <-l.pause 之前发生在每个loop协程。这应该意味着 make(chan struct{})值作为 l.resume 的值可见在所有这些loop goroutines,以同样的方式字符串 "hello world"作为 a 的值可见在f文档示例中的 goroutine。


一些可能相关的附加信息:

  • 如果我替换 l.resumeunsafe.Pointer并访问 chan struct{}atomic.LoadPointerloopatomic.StorePointerwhilePaused , 比赛结束了。这似乎提供了与 channel 应该提供的完全相同的获取-释放顺序?

  • 如果我添加 time.Sleep(10 * time.Microsecond)l.paused.Done() 之间和 <-l.resume , 程序通常在调用 fn 后死锁一两次。

  • 如果我添加 fmt.Printf(".")相反,程序打印 28 . s,调用第一个函数,打印另一个 32 . s,然后挂起(或者偶尔调用第二个函数,然后打印另一个 32 . s 并挂起)。


这是我的其余代码,如果你想运行整个代码:

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

// looper code from above

var n int64    
func dostuff() {
    atomic.AddInt64(&n, 1)
}

func main() {
    l := &looper{
        pause: make(chan struct{}),
    }
    var init sync.WaitGroup
    init.Add(32)
    for i := 0; i < 32; i++ {
        go func() {
            init.Done()
            l.loop()
        }()
    }
    init.Wait()
    for i := 0; i < 100; i++ {
        l.whilePaused(func() { fmt.Printf("%d ", i) })
    }
    fmt.Printf("\n%d\n", atomic.LoadInt64(&n))
}

最佳答案

这是因为在线程执行 l.paused.Done() 之后,另一个线程能够绕过循环并再次分配 l.resume

这是操作顺序

Looper thread    |    Pauser thread
------------------------------------
l.paused.Done()  |   
                 |   l.paused.Wait()
                 |   l.pause = make(chan struct{})
                 |   round the loop
                 |   l.paused.Add(numThreads)
<- l.resume      |   l.resume = make(chan struct{})   !!!RACE!!

关于go - Race 暂停一组 goroutines,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50244690/

相关文章:

java - 如何同步两个队列到线程构造函数的传递?

c - 在 macOS 内核扩展中有效地使用同步

go - 了解 golang channel 。所有的goroutines都睡着了——死锁【围棋之旅,爬虫】

go - 在 golang 中结合时间和日期?

html - 如何使用字符串标记器替换特定的html标签

json - 如何在 Go 中将 byte/uint8 数组编码为 json 数组?

GO 解析嵌套的 json 数组

c - 进程间共享内存和 pthread_barrier : how to be safe?

for-loop - 如何在Go中并行运行for循环内的方法?

go - 确保 goroutine 清理,最佳实践