【问题标题】:Golang intermittent behaviour on timed out GoroutineGolang在超时Goroutine上的间歇性行为
【发布时间】:2019-11-19 19:27:20
【问题描述】:

我正在尝试为重复性任务实现并发。我想在不同的 Goroutine 上实现一个 http 请求(由longRunningTask 函数描绘)。我提供了一个计时器,用于停止 Goroutine 并向主 Goroutine 发送超时信号,如果重负载任务继续预定义的超时。我目前遇到的问题是我出现间歇性行为。

代码已简化为如下所示。

package main

import (
    "fmt"
    "time"
)

func main() {
    var iteration int = 5

    timeOutChan := make(chan struct{})
    resultChan := make(chan string)

    for i := 0; i < iteration; i++ {
        go longRunningTaks(timeOutChan, resultChan)
    }

    for i := 0; i < iteration; i++ {
        select {
        case data := <-resultChan:
            fmt.Println(data)
        case <-timeOutChan:
            fmt.Println("timed out")
        }
    }

}

func longRunningTaks(tc chan struct{}, rc chan string) {
    timer := time.NewTimer(time.Nanosecond * 1)
    defer timer.Stop()

    // Heavy load task
    time.Sleep(time.Second * 1)

    select {
    case <-timer.C:
        tc <- struct{}{}
    case rc <- "success":
        return
    }
}

我相信每次尝试都应该打印出来

timeout
timeout
timeout
timeout
timeout

相反,我得到了间歇性

success
timeout
timeout
timeout
timeout

【问题讨论】:

  • "If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection." rc 上的发送很可能会继续进行,因为 main 中有匹配的接收。您的程序根本就不是确定性的。
  • @Peter 我不太明白,当睡眠时间长于超时时间时,rc 如何进行?另外,您所说的不仅仅是确定性是什么意思?
  • @Adam 您的“超时”不会超时。您总是等待睡眠完成,然后检查前一段时间是否发生了“超时”;您在查看是否可以发送结果的同一步骤中进行检查。两者都是可能的,选择哪一个是完全任意的。这不是您在 Go 中执行超时的方式。

标签: go concurrency goroutine channels


【解决方案1】:

文档提到:

NewTimer 创建一个新的 Timer,它将在其上发送当前时间 至少持续 d 之后的频道。

“至少意味着”计时器肯定会花费指定的时间,但这也隐含地意味着可能花费比指定更多的时间。 Timer 启动自己的 go 例程,并在到期时写入通道。 由于调度程序或垃圾收集或写入其他通道的过程可能会延迟。此外,考虑到上述可能性,模拟工作量非常短。

更新

正如 Peter 在评论中提到的,向 rc 通道写入“成功”是同样可能完成的操作,因为可以通过主程序从另一端读取。选择必须在 1) 将“成功”写入 rc 通道和 2) 过期计时器之间进行选择。两者都是可能的。 一开始的可能性更大,因为主程序还没有从另一端读取它。一旦发生这种情况。其他剩余的例程将不得不竞争通道(写入“成功”)(因为它阻塞了缓冲区大小 0),因此在其余时间中,选择过期计时器的可能性更多不能说主程序从 resultChan 通道(rc 的另一端)读取的速度有多快。

【讨论】:

  • 如果真的是这个原因,我觉得应该不会比睡觉更费时间。
  • 正如彼得提到的rc &lt;- "success" 也是select 针对过期计时器的同样合格事件。我没有意识到这一点。 select 调用 1) 计时器过期或 2) 将“成功”写入可从另一端读取的通道,是 2 个同等竞争的事件。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-05
  • 2012-10-20
  • 1970-01-01
相关资源
最近更新 更多