监视器goroutine中的递归发送

时间:2016-02-27 11:59:32

标签: go concurrency deadlock

在我编写的定时器的简单调度程序中,我正在使用监视器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
    }
}

这很有效,直到发送一个发送另一个动作的动作。 计划的操作可以安排另一个操作,当达到缓冲区大小时会导致死锁。

是否有任何干净的方法来解决这个问题而不诉诸共享状态(我已经在我的特定问题中尝试过,但是非常难看)?

1 个答案:

答案 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
    })
}