强制优先选择语句 [英] force priority of go select statement

查看:151
本文介绍了强制优先选择语句的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下代码片段:

  func sendRegularHeartbeats(ctx context.Context) {
for {
select {
case< -ctx.Done():
return
case< -time.After(1 * time.Second) :
sendHeartbeat()
}
}
}

此功能在专用的执行程序中执行并每秒发送一次心跳消息。当上下文被取消时,整个过程应该立即停止。



现在考虑以下情形:

  ctx,cancel:= context.WithCancel(context.Background())
cancel()
go sendRegularHeartbeats(ctx)

这将启动具有封闭上下文的心跳程序。在这种情况下,我不希望传输心跳。因此select中的第一个 case 块应该立即输入。

然而,看起来顺序 case 块的评估不能保证,并且代码有时会发送心跳消息,即使上下文已被取消。



实现这种行为的正确方法是什么?

我可以在第二个案例中添加一个isContextclosed - 检查,但看起来更像是一个丑陋的解决方法。

解决方案

提前注意: 您的示例将按照您的意图工作,当调用 sendRegularHeartbeats()时,上下文已经被取消, case< -ctx.Done()通信将会唯一一个准备好进行,因此选择。其他案例< -time.After(1 * time.Second)只会在1秒后继续 ,因此它不会首先被选中。但是,在多个案件准备就绪的情况下,为了明确处理优先事项,请继续阅读。 案例不同, $ b switch的分支声明(其中评估订单是他们列出的订单)时,没有优先级或任何订单保证在情况分支中https://golang.org/ref/spec#Select_statementsrel =nofollow noreferrer> select statement

引用:Select语句:


如果一个或多个通信可以继续,则可以通过统一的伪随机选择选择一个可以继续的通信。否则,如果存在默认情况,则选择该情况。如果没有默认情况下,选择语句会阻塞,直到至少有一个通信可以继续。


继续,随机选择一个。期间。



如果您想保持优先权,您必须自己手动执行此操作。您可以使用多个选择语句(后面的,不是嵌套的),在 之前列出具有更高优先级的语句 select ,还要确保添加一个默认分支以避免阻塞,如果这些分支还没有准备好进行。你的例子需要2 选择语句,首先检查< -ctx.Done(),因为那是你的希望获得更高的优先级。



我还建议使用一个 time.Ticker 而不是调用 time.After() 在每次迭代中( time.After())也使用 time.Ticker ,但它不会重复使用,只是扔掉并在下次调用时创建一个新的。)



下面是一个示例实现:

  func sendRegularHeartbeats(ctx context.Context){
ticker:= time.NewTicker(time.Second)
defer ticker.Stop()

for {
select {
case< -ctx.Done( ):
返回
默认值:
}

选择{
ca se< -ctx.Done():
return
case< -ticker.C:
sendHeartbeat()
}
}
}



如果在 sendRegularHeartbeats()<>时取消了上下文, / code>被调用,因为您可以在 Go Playground

上检查/ a>。



如果您将 cancel()呼叫延迟2.5秒,则会发送2个心跳:

  ctx,cancel:= context.WithCancel(context.Background())
go sendRegularHeartbeats(ctx)
time.Sleep(time.Millisecond * 2500)
cancel()
time.Sleep(time.Second * 2)

试试 Go Playground

a>。


I have the following piece of code:

func sendRegularHeartbeats(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            return
        case <-time.After(1 * time.Second):
            sendHeartbeat()
        }
    }
}

This function is executed in a dedicated go-routine and sends a heartbeat-message every second. The whole process should stop immediately when the context is canceled.

Now consider the following scenario:

ctx, cancel := context.WithCancel(context.Background())
cancel()
go sendRegularHeartbeats(ctx)

This starts the heartbeat-routine with a closed context. In such a case, I don't want any heartbeats to be transmitted. So the first case block in the select should be entered immediately.

However, it seems that the order in which case blocks are evaluated is not guaranteed, and that the code sometimes sends a heartbeat message, even though the context is already canceled.

What is the correct way to implement such a behaviour?

I could add a "isContextclosed"-check in the second case, but that looks more like an ugly workaround for the problem.

解决方案

Note beforehand:

Your example will work as you intend it to, as if the context is already cancelled when sendRegularHeartbeats() is called, the case <-ctx.Done() communication will be the only one ready to proceed and therefore chosen. The other case <-time.After(1 * time.Second) will only be ready to proceed after 1 second, so it will not be chosen at first. But to explicitly handle priorities when multiple cases might be ready, read on.


Unlike the case branches of a switch statement (where the evaluation order is the order they are listed), there is no priority or any order guaranteed in the case branches of a select statement.

Quoting from Spec: Select statements:

If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the "select" statement blocks until at least one of the communications can proceed.

If more communications can proceed, one is chosen randomly. Period.

If you want to maintain priority, you have to do that yourself (manually). You may do it using multiple select statements (subsequent, not nested), listing ones with higher priority in an earlier select, also be sure to add a default branch to avoid blocking if those are not ready to proceed. Your example requires 2 select statements, first one checking <-ctx.Done() as that is the one you want higher priority for.

I also recommend using a single time.Ticker instead of calling time.After() in each iteration (time.After() also uses a time.Ticker under the hood, but it doesn't reuse it just "throws it away" and creates a new one on the next call).

Here's an example implementation:

func sendRegularHeartbeats(ctx context.Context) {
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return
        default:
        }

        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            sendHeartbeat()
        }
    }
}

This will send no heartbeat if the context is already cancelled when sendRegularHeartbeats() is called, as you can check / verify it on the Go Playground.

If you delay the cancel() call for 2.5 seconds, then exactly 2 heartbeats will be sent:

ctx, cancel := context.WithCancel(context.Background())
go sendRegularHeartbeats(ctx)
time.Sleep(time.Millisecond * 2500)
cancel()
time.Sleep(time.Second * 2)

Try this one on the Go Playground.

这篇关于强制优先选择语句的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆