如何在Goroutine中退出超时任务?代码问题与优化建议咨询
代码问题排查与优化建议
现存问题
- run函数无法响应超时中断:当前run函数里的
select仅在执行开始时检查一次ctx.Done(),一旦进入default分支执行time.Sleep,即使ctx超时,sleep也会继续执行直到结束,无法及时退出。 - goroutine泄漏与退出不优雅:
do通道未关闭,导致3个work goroutine会一直阻塞在for v := range do处;main函数用time.Sleep(20s)等待任务完成,属于硬编码等待,无法准确感知任务结束时间。 - rand.Seed冗余(Go1.20+):从Go 1.20开始,
math/rand包默认会自动初始化随机种子,无需手动调用rand.Seed。
优化后的代码
package main import ( "context" "log" "math/rand" "sync" "time" ) func main() { s := []int{100, 200, 300, 400, 500, 600} do := make(chan int, len(s)) // 改为带缓冲通道,避免发送阻塞 var wg sync.WaitGroup ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) defer cancel() work := func(ctx context.Context) { defer wg.Done() for v := range do { run(v, ctx) } } workerCount := 3 wg.Add(workerCount) for range [3]struct{}{} { go work(ctx) } // 发送任务到通道,同时监听超时 for _, v := range s { select { case do <- v: case <-ctx.Done(): log.Println("ctx timeout, stop sending tasks") break } } close(do) // 关闭通道,让work goroutine的for range循环退出 wg.Wait() // 等待所有worker完成任务 log.Println("all tasks finished") } func run(v int, ctx context.Context) { log.Printf("start processing %d", v) // 用select同时监听ctx.Done()和任务完成事件 select { case <-ctx.Done(): log.Printf("task %d timeout", v) return case <-time.After(time.Duration(rand.Intn(5)+1) * time.Second): log.Printf("task %d finished", v) } }
优化说明
- run函数响应超时:将
time.Sleep替换为time.After并放入select分支,这样在任务执行期间一旦ctx超时,<-ctx.Done()会立即触发,任务可以及时退出。 - 优雅退出与避免泄漏:使用
sync.WaitGroup跟踪所有worker goroutine的状态,关闭do通道让worker的for循环正常退出,替代硬编码的time.Sleep。 - 通道优化:将
do改为带缓冲通道,容量等于任务数量,避免main函数在发送任务时因worker未及时接收而阻塞。 - 任务发送容错:在发送任务时也监听
ctx.Done(),如果超时则停止发送剩余任务。
内容的提问来源于stack exchange,提问作者alex
相关产品推荐
相关产品推荐

