Golang中如何彻底终止ctx超时后仍在运行的go func()
Golang中如何彻底终止ctx超时后仍在运行的go func()
package main
import (
"context"
"log"
"time"
)
func longRunningCalculation(timeCost int)chan string{
result:=make(chan string)
go func (){
time.Sleep(time.Second*(time.Duration(timeCost)))
log.Println("Still doing other things...")//Even if it times out, this goroutine is still doing other tasks.
result<-"Done"
log.Println(timeCost)
}()
return result
}
func jobWithTimeout(){
ctx,cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
select{
case <-ctx.Done():
log.Println(ctx.Err())
return
case result:=<-longRunningCalculation(3):
log.Println(result)
}
}
func main() {
jobWithTimeout()
time.Sleep(time.Second*5)
}
预期看到什么?
2019/09/25 11:00:16 context deadline exceeded
实际看到什么?
2019/09/25 11:00:16 context deadline exceeded 2019/09/25 11:00:17 Still doing other things…
更多关于Golang中如何彻底终止ctx超时后仍在运行的go func()的实战教程也可以访问 https://www.itying.com/category-94-b0.html
kync:
不,这样做不好,标准日志库已经处理了并发调用,不需要添加额外的同步。
标准库能够安全处理并发调用,但这并不意味着它保证了输出的一致性。决定因素取决于原帖作者如何控制他/她的输出。
更多关于Golang中如何彻底终止ctx超时后仍在运行的go func()的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
你应该将上下文传递给 longRunningCalculation,然后在那里检查取消操作。但在某些情况下,你无法避免超时,例如,如果你正在调用一个库函数,而该函数执行繁重的操作,你必须等待它,或者你需要处理 context deadline exceeded 错误。如果你正在进行一些递归或循环任务,可以在每次递归调用或循环迭代时检查上下文,以决定是否需要取消。
但是,如果我在程序开始和结束时打印协程数量,我仍然看到有3个孤立的协程,这是否是一种资源浪费?
func main() {
timeout := 2
cost := 3
fmt.Println(runtime.NumGoroutine())
jobWithTimeout(timeout, func(done context.CancelFunc) {
longRunningCalculation(cost)
done()
})
fmt.Println(runtime.NumGoroutine())
}
添加一个终止通道
你需要从 jobWithTimeout 向 longRunningCalcuation 传递另一个信号通道(终止通道),并将 longRunningCalculation 放入那个 goroutine 中。其风格与 jobWithTimeout 中的 select 相同。
然后,当执行到 <-ctx.Done() 这个 case 时,发送终止信号。各个 goroutine 将分别处理结束操作。
编辑:不要采用这个方法。手动工作量太大了。
另一个发现的问题:管理输出
使用一个通道来控制日志输出(log.Println)。应该只有一个函数负责从该通道读取数据并处理 log.Println。
在Go中,要彻底终止超时后仍在运行的goroutine,需要使用context的取消机制来通知goroutine停止执行。你的代码中虽然使用了context.WithTimeout,但没有将ctx传递给longRunningCalculation函数,因此goroutine无法感知到取消信号。
以下是修改后的代码示例:
package main
import (
"context"
"log"
"time"
)
func longRunningCalculation(ctx context.Context, timeCost int) chan string {
result := make(chan string)
go func() {
select {
case <-time.After(time.Second * time.Duration(timeCost)):
log.Println("Still doing other things...")
result <- "Done"
log.Println(timeCost)
case <-ctx.Done():
log.Println("Goroutine canceled:", ctx.Err())
result <- "Canceled"
return
}
}()
return result
}
func jobWithTimeout() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
select {
case <-ctx.Done():
log.Println(ctx.Err())
return
case result := <-longRunningCalculation(ctx, 3):
log.Println(result)
}
}
func main() {
jobWithTimeout()
time.Sleep(time.Second * 5)
}
关键修改:
- 将
context.Context作为参数传递给longRunningCalculation函数 - 在goroutine中使用
select语句同时监听超时和context取消信号 - 当
ctx.Done()被触发时,goroutine会立即返回,不再执行后续操作
这样修改后,当context超时时,goroutine会立即停止执行,不会打印"Still doing other things…"。输出结果将只有:
2019/09/25 11:00:16 context deadline exceeded
2019/09/25 11:00:16 Goroutine canceled: context deadline exceeded
如果需要更细粒度的控制,可以在goroutine中定期检查ctx.Err()或使用ctx.Done()通道来提前退出循环或长时间运行的操作。


