我正在尝试为重复任务实现并发。我想在不同的 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
你提到:
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
}
}
中提到
在 Go 1.23 之前,当不再使用
时,我们必须这样做:Timer
aTimer.Stop() select { case <-aTimer.C: default: }
让计时器可收集。
从 Go 1.23 开始,只需停止计时器即可。
另请参阅:
time
:在 Timer/Ticker
或 Stop
返回后,无法以旧值接收
Reset
time:
之后,Timer.C
Timer.Reset
仍然可以触发这两个问题都应该在 Go 1.23(2024 年第 3 季度)中得到解决。
医生提到:
NewTimer 创建一个新的计时器,它将在其上发送当前时间 至少持续时间 d 之后的频道。
“至少意味着”计时器肯定会花费指定的时间,但这也隐含意味着可能需要比指定的时间更多的时间。计时器启动自己的 go 例程并在到期时写入通道。 由于调度程序或垃圾收集或写入其他通道的过程可能会延迟。此外,考虑到上述可能性,模拟工作量非常短。
更新:
正如 Peter 在评论中提到的,向 rc 通道写入“成功”是同样可能完成的操作,因为可以通过主例程从另一端读取该操作。 select 必须在 1) 将“成功”写入 rc 通道和 2) 计时器过期之间进行选择。两者都是可能的。 一开始出现No1的可能性更大,因为主程序还没有从另一端读取它。一旦发生这种情况。其他剩余的例程将必须竞争通道(写入“成功”)(因为它正在阻塞,缓冲区大小为0),因此在其余时间,过期计时器被选择的可能性更大,因为不能说有多快主例程将从 resultChan 通道(rc 的另一端)读取。