我有一堆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)
}
我启动了所有正在运行loop()
的32个goroutine,然后连续100次调用whilePaused
,一切似乎都有效......但如果我用-race
运行它,它会告诉我在l.resume
(whilePaused
)中写入并在l.resume = make(chan struct{})
(loop
)中阅读时,<-l.resume
上有一场竞赛。
我不明白为什么会这样。根据{{3}},close(l.pause)
应该在每个<-l.pause
goroutine loop
之前发生。这应该意味着make(chan struct{})
值在所有l.resume
goroutine中显示为loop
的值,就像字符"hello world"
作为{的值可见一样{1}}文档示例中的a
goroutine。{/}
可能相关的一些其他信息:
如果我将f
替换为l.resume
,并使用unsafe.Pointer
中的chan struct{}
和atomic.LoadPointer
loop
访问atomic.StorePointer
值{1}},比赛消失了。这似乎提供了通道已经提供的完全相同的获取 - 释放顺序?
如果我在whilePaused
和time.Sleep(10 * time.Microsecond)
之间添加l.paused.Done()
,程序通常会在调用<-l.resume
一两次后死锁。
如果我添加fn
,程序将打印28 fmt.Printf(".")
s,调用第一个函数,打印另外32个.
,然后挂起(或偶尔,调用第二个函数,然后打印另外32个.
并挂起)。
以下是我的其余代码,以防您想要运行整个代码:
.
答案 0 :(得分:4)
This is because after the thread does l.paused.Done(), the other thread is able to go around the loop and assign l.resume again
Here is sequence of operations
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!!