douti9286 2016-02-27 11:59
浏览 33
已采纳

递归发送监视器goroutine

In a simple scheduler for timers I'm writing, I'm making use of a monitor goroutine to sync start/stop and timer done events.

The monitor goroutine, when stripped down to the essential, looks like this:

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
    }
}

This works great, until an action is sent that sends another action. It's possible for a scheduled action to schedule another action, which causes a deadlock when the buffer size is reached.

Is there any clean way to solve this issue without resorting to shared state (which I've tried in my particular problem, but is quite ugly)?

  • 写回答

1条回答 默认 最新

  • dongtang5229 2016-02-27 12:10
    关注

    The problem arises from the fact that when your monitor goroutine "takes out" (receives) a value (a function) and executes it (this also happens on the monitor goroutine), during its execution it sends a value on the monitored channel.

    This in itself would not cause deadlock as when the function is executed (a()), it is already taken out of the buffered channel, so there is at least one free space in it, so sending a new value on the channel inside a() could proceed without blocking.

    Problem may arise if there are other goroutines which may also send values on the monitored channel, which is your case.

    One way to avoid the deadlock is that if the function being executed tries to "put back" (send) a function not in the same goroutine (which is the monitor goroutine), but in a new goroutine, so the monitor goroutine is not blocked:

    actions <- func() {
        // Send new func value in a new goroutine:
        // this will never block the monitor goroutine
        go func() {
            actions <- func() {
                // No deadlock.
            }
        }()
    }
    

    By doing this, the monitor goroutine will not be blocked even if buffer of actions is full, because sending a value on it will happen in a new goroutine (which may be blocked until there is free space in the buffer).

    If you want to avoid always spawning a new goroutine when sending a value on actions, you may use a select to first try to send it without spawning a new goroutine. Only when buffer of actions is full and cannot be sent without blocking, shall you spawn a goroutine for sending to avoid deadlock, which - depending on your actual case may happen very rarely, and in this case it's inevitable anyway to avoid deadlock:

    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
            }()
        }
    }
    

    If you have to do this in many places, it's recommended to create a helper function for it:

    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
            }()
        }
    }
    

    And using it:

    actions <- func() {
        safeSend(func() {
            // something to do
        })
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 想问一下树莓派接上显示屏后出现如图所示画面,是什么问题导致的
  • ¥100 嵌入式系统基于PIC16F882和热敏电阻的数字温度计
  • ¥15 cmd cl 0x000007b
  • ¥20 BAPI_PR_CHANGE how to add account assignment information for service line
  • ¥500 火焰左右视图、视差(基于双目相机)
  • ¥100 set_link_state
  • ¥15 虚幻5 UE美术毛发渲染
  • ¥15 CVRP 图论 物流运输优化
  • ¥15 Tableau online 嵌入ppt失败
  • ¥100 支付宝网页转账系统不识别账号