在我编写的定时器的简单调度程序中,我正在使用监视器goroutine来同步启动/停止和计时器完成事件。
显示器goroutine,当被剥离到必要时,看起来像这样:
actions := make(chan func(), 1024)
// monitor goroutine
go func() {
for a := range actions {
a()
}
}()
actions <- func() {
actions <- func() {
// causes deadlock when buffer size is reached
}
}
这很有效,直到发送一个发送另一个动作的动作。 计划的操作可以安排另一个操作,当达到缓冲区大小时会导致死锁。
是否有任何干净的方法来解决这个问题而不诉诸共享状态(我已经在我的特定问题中尝试过,但是非常难看)?
答案 0 :(得分:0)
问题源于这样一个事实:当你的监视器goroutine“取出”(接收)一个值(一个函数)并执行它时(这也发生在监视器goroutine上),在执行期间它会在监视器上发送一个值信道。
这本身不会导致死锁,因为当执行函数(a()
)时,它已经从缓冲通道中取出,因此其中至少有一个空闲空间,因此发送一个新值在a()
内的频道上可以不受阻挡地继续进行。
如果还有其他goroutine也可能会在受监控的频道上发送值,则可能会出现问题,这是您的情况。
避免死锁的一种方法是,如果正在执行的函数试图“放回”(发送)不在同一个goroutine(即监视器goroutine)中的函数,而是在新的goroutine中,那么监视器goroutine没有被阻止:
actions <- func() {
// Send new func value in a new goroutine:
// this will never block the monitor goroutine
go func() {
actions <- func() {
// No deadlock.
}
}()
}
通过执行此操作,即使actions
的缓冲区已满,监视器goroutine也不会被阻止,因为在它上面发送一个值将在新的goroutine中发生(可能会被阻塞,直到有空闲空间)缓冲液)。
如果您想避免在actions
上发送值时始终生成新的goroutine,您可以使用select
首先尝试发送它而不会产生新的goroutine。只有当actions
的缓冲区已满并且无法无阻塞地发送时,您是否会产生一个goroutine用于发送以避免死锁,这取决于您的实际情况可能很少发生,并且在这种情况下,无论如何都要避免死锁:
actions <- func() {
newfv := func() { /* do something */ }
// First try to send with select:
select {
case actions <- newfv:
// Success!
default:
// Buffer is full, must do it in new goroutine:
go func() {
actions <- newfv
}()
}
}
如果你必须在很多地方这样做,建议为它创建一个辅助函数:
func safeSend(fv func()) {
// First try to send with select:
select {
case actions <- fv:
// Success!
default:
// Buffer is full, must do it in new goroutine:
go func() {
actions <- fv
}()
}
}
使用它:
actions <- func() {
safeSend(func() {
// something to do
})
}