在监视器 goroutine 中递归发送

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

    }

}

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


有没有什么干净的方法来解决这个问题而不诉诸共享状态(我在我的特定问题中尝试过,但很丑陋)?


30秒到达战场
浏览 138回答 1
1回答

茅侃侃

问题源于这样一个事实,即当您的监控 goroutine “取出”(接收)一个值(一个函数)并执行它(这也发生在监控 goroutine 上)时,它在执行过程中会在受监控的通道上发送一个值。这本身不会导致死锁,因为函数执行时( a()),它已经从缓冲通道中取出,因此其中至少有一个空闲空间,因此在通道内部发送新值a()可以继续进行而不会阻塞.如果还有其他 goroutines 也可能在受监控的通道上发送值,则可能会出现问题,这就是您的情况。避免死锁的一种方法是,如果正在执行的函数试图“放回”(发送)一个不在同一个 goroutine(即监视器 goroutine)中的函数,而是在一个新的 goroutine 中,那么监视器 goroutine 不会被阻塞:actions <- func() {&nbsp; &nbsp; // Send new func value in a new goroutine:&nbsp; &nbsp; // this will never block the monitor goroutine&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; actions <- func() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // No deadlock.&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }()}通过这样做,即使缓冲区actions已满,monitor goroutine 也不会被阻塞,因为在它上面发送一个值将发生在一个新的 goroutine 中(可能会被阻塞,直到缓冲区中有可用空间)。如果你想避免在发送一个值时总是产生一个新的 goroutine actions,你可以使用 aselect来首先尝试发送它而不产生一个新的 goroutine。仅当缓冲区actions已满且无法在不阻塞的情况下发送时,您才应生成用于发送的 goroutine 以避免死锁,这取决于您的实际情况可能很少发生,在这种情况下无论如何避免死锁是不可避免的:actions <- func() {&nbsp; &nbsp; newfv := func() { /* do something */ }&nbsp; &nbsp; // First try to send with select:&nbsp; &nbsp; select {&nbsp; &nbsp; case actions <- newfv:&nbsp; &nbsp; &nbsp; &nbsp; // Success!&nbsp; &nbsp; default:&nbsp; &nbsp; &nbsp; &nbsp; // Buffer is full, must do it in new goroutine:&nbsp; &nbsp; &nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; actions <- newfv&nbsp; &nbsp; &nbsp; &nbsp; }()&nbsp; &nbsp; }}如果很多地方都需要这样做,建议为其创建一个辅助函数:func safeSend(fv func()) {&nbsp; &nbsp; // First try to send with select:&nbsp; &nbsp; select {&nbsp; &nbsp; case actions <- fv:&nbsp; &nbsp; &nbsp; &nbsp; // Success!&nbsp; &nbsp; default:&nbsp; &nbsp; &nbsp; &nbsp; // Buffer is full, must do it in new goroutine:&nbsp; &nbsp; &nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; actions <- fv&nbsp; &nbsp; &nbsp; &nbsp; }()&nbsp; &nbsp; }}并使用它:actions <- func() {&nbsp; &nbsp; safeSend(func() {&nbsp; &nbsp; &nbsp; &nbsp; // something to do&nbsp; &nbsp; })}
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go