Golang 在超时 Goroutine 上的间歇性行为

问题描述 投票:0回答:2

我正在尝试为重复任务实现并发。我想在不同的 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
go concurrency goroutine channel
2个回答
0
投票

你提到:

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

Tapir Liui他的推文

中提到

在 Go 1.23 之前,当不再使用

Timer
时,我们必须这样做:

aTimer.Stop()
select {
       case <-aTimer.C:
       default:
}

让计时器可收集。

从 Go 1.23 开始,只需停止计时器即可。

另请参阅:

  • 问题 37196
    time
    :在
    Timer/Ticker
    Stop
    返回
    后,无法以旧值接收 
    Reset
  • 通道
  • 问题11513:即使在调用
    time:
    之后,
    Timer.C
    Timer.Reset
    仍然可以触发

这两个问题都应该在 Go 1.23(2024 年第 3 季度)中得到解决。


-1
投票

医生提到:

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

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

更新

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

© www.soinside.com 2019 - 2024. All rights reserved.