【问题标题】:Golang: using multiple tickers cases in single select blocks entire loopGolang:在单个选择中使用多个代码案例阻止整个循环
【发布时间】:2021-11-10 21:10:19
【问题描述】:

我有一个要求,我需要定期做多项事情(此处无关)。我使用下面提到的代码块实现了它-

func (processor *Processor) process() {
    defaultTicker := time.NewTicker(time.Second*2)
    updateTicker := time.NewTicker(time.Second*5)
    heartbeatTicker := time.NewTicker(time.Second*5)
    timeoutTicker := time.NewTicker(30*time.Second)
    refreshTicker := time.NewTicker(2*time.Minute)
    defer func() {
        logger.Info("processor for ", processor.id, " exited")
        defaultTicker.Stop()
        timeoutTicker.Stop()
        updateTicker.Stop()
        refreshTicker.Stop()
        heartbeatTicker.Stop()
    }()
    for {
        select {
        case <-defaultTicker.C:
            // spawn some go routines
        case <-updateTicker.C:
            // do something
        case <-timeoutTicker.C:
            // do something else
        case <-refreshTicker.C:
            // log
        case <-heartbeatTicker.C:
            // push metrics to redis
        }
    }
}

但我注意到每隔一段时间,我的 for select 循环就会在某处卡住,我似乎无法找到位置或原因。 卡住是指我停止接收刷新代码日志。但它会在一段时间后(5-10 分钟)再次开始正常工作

我已确保每个代码中的所有操作都在极短的时间内完成(约 0 毫秒,通过放置日志进行检查)。

我的问题:

  1. 在单选中使用多个代码是一种良好/正常的做法(老实说,我在网上没有找到很多使用多个代码的示例)
  2. 任何知道任何已知问题/陷阱的人都可以在这些问题/陷阱中长时间阻塞循环。

感谢任何帮助。谢谢

【问题讨论】:

  • 根据您提供的信息,最可能的原因是其中一个案例花费的时间比您预期的要长。
  • 让所有这些代码由单个 select 处理而不是每个代码单独的 goroutine 有什么好处?如果您正在执行一些长时间运行的操作,例如refreshTicker,在此操作完成之前不会处理其他代码。
  • @aquaman 你解决了这个问题吗?我会对原因和解决方案感兴趣。
  • @Juve 是的,我能够解决它。问题在于“case”内部调用的函数之一。我们使用 tickers 定期处理 LRU,在某些情况下 LRU 的实现不正确导致无限循环 :( 修复问题后,ticters 不再有问题 :)
  • 很高兴听到@aquaman。我刚刚发布了对您的问题的较长回答,但想等到您确认我的假设。我希望它有助于更​​好地理解这个主题。

标签: go concurrency ticker


【解决方案1】:

Go 没有为多个通道提供任何智能排空行为,例如,一个通道中较旧的消息会比其他通道中较新的消息更早得到处理。每当循环进入select 语句时,就会选择一个随机通道。

另请参阅this answer 并阅读有关GOMAXPROCS=1 的部分。这可能与您的问题有关。问题也可能在您的日志记录包中。也许日志只是延迟了。 一般来说,我认为问题一定出在您的case 语句中。要么您有阻塞功能,要么有一些功能失调的代码。 (注意:由 OP 确认)

但要回答你的问题:

1.在单选中使用多个代码是一种良好/正常的做法吗?

通常以阻塞的方式从多个通道中随机读取,一次一条消息,例如,将来自多个通道的传入数据排序到切片或映射中,并避免并发数据访问。 添加一个或多个代码也很常见,例如,用于刷新数据以及用于记录或报告。通常非股票代码路径将完成大部分工作。

在您的情况下,您使用的代码将运行彼此应该阻塞的代码路径,这是一个非常具体的用例,但在某些情况下可能需要。我认为这种不常见但也不错的做法。 正如评论者所建议的那样,您还可以在不同的 goroutine 中安排不同的重复任务。

2。有没有人知道任何已知的问题/陷阱,其中代码可以长时间阻塞循环?

代码本身不会以任何隐藏的方式阻塞循环。最快的代码将始终确保循环至少以该代码的速度循环。

注意time.NewTicker 的文档说:

自动收报机将调整时间间隔或丢弃刻度线以弥补 慢接收器

这只是意味着,在您从单元素报价器通道消耗完最后一个报价之前,内部不会安排新报价。

在您的示例中,主要缺陷是case 语句中的任何代码都会阻塞,从而延迟其他情况。 如果这是故意的,一切都很好。

如果您有微秒或纳秒代码,您可能会看到一些可测量的运行时开销,或者如果您有数百个代码和case 块,则可能存在其他陷阱。但是你应该从一开始就选择另一种调度模式。

【讨论】:

    猜你喜欢
    • 2020-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-28
    • 2012-12-07
    • 2014-09-09
    • 2014-07-29
    • 2017-12-11
    相关资源
    最近更新 更多