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

6 回复

kync:

不,这样做不好,标准日志库已经处理了并发调用,不需要添加额外的同步。

标准库能够安全处理并发调用,但这并不意味着它保证了输出的一致性。决定因素取决于原帖作者如何控制他/她的输出。

更多关于Golang中如何彻底终止ctx超时后仍在运行的go func()的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你应该将上下文传递给 longRunningCalculation,然后在那里检查取消操作。但在某些情况下,你无法避免超时,例如,如果你正在调用一个库函数,而该函数执行繁重的操作,你必须等待它,或者你需要处理 context deadline exceeded 错误。如果你正在进行一些递归或循环任务,可以在每次递归调用或循环迭代时检查上下文,以决定是否需要取消。

另一个发现的问题:管理输出

不,这不好,标准日志库已经处理了并发调用,不需要添加额外的同步。

图片

go.dev

图片

- The Go Programming Language

但是,如果我在程序开始和结束时打印协程数量,我仍然看到有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)
}

关键修改:

  1. context.Context作为参数传递给longRunningCalculation函数
  2. 在goroutine中使用select语句同时监听超时和context取消信号
  3. 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()通道来提前退出循环或长时间运行的操作。

回到顶部