funcdoJobs(){result:=make(chanint)forjobId:=0;jobId<10;jobId++{gohardJob(jobId,result)}ctx,cancelFunc:=context.WithTimeout(context.Background(),time.Second*3)defercancelFunc()loop:for{select{case_=<-result:log.Println("a job has done")case<-ctx.Done():ctx.Deadline()log.Printf("got context err: %s\n",ctx.Err())breakloop}}// going on
select{}}funchardJob(jobIdint,chchan<-int){log.Printf("a worker is doning hard job:[%d]\n",jobId)time.Sleep(time.Second*time.Duration(rand.Int31()%10))ch<-jobId}
这段程序的输出是这样的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
> go test -v . -count=1 -run TestLeakTest=== RUN TestLeakTest
2021/10/25 21:43:49 leak_test.go:46: a worker is doning hard job:[2]2021/10/25 21:43:49 leak_test.go:46: a worker is doning hard job:[9]2021/10/25 21:43:49 leak_test.go:46: a worker is doning hard job:[3]2021/10/25 21:43:49 leak_test.go:46: a worker is doning hard job:[4]2021/10/25 21:43:49 leak_test.go:46: a worker is doning hard job:[5]2021/10/25 21:43:49 leak_test.go:46: a worker is doning hard job:[6]2021/10/25 21:43:49 leak_test.go:34: a job has done2021/10/25 21:43:49 leak_test.go:46: a worker is doning hard job:[7]2021/10/25 21:43:49 leak_test.go:46: a worker is doning hard job:[8]2021/10/25 21:43:49 leak_test.go:46: a worker is doning hard job:[0]2021/10/25 21:43:49 leak_test.go:46: a worker is doning hard job:[1]2021/10/25 21:43:50 leak_test.go:34: a job has done2021/10/25 21:43:51 leak_test.go:34: a job has done2021/10/25 21:43:51 leak_test.go:34: a job has done2021/10/25 21:43:52 leak_test.go:34: a job has done2021/10/25 21:43:52 leak_test.go:37: got context err: context deadline exceeded
funchardJobWithCtx(ctxcontext.Context,jobIdint,chchan<-int){log.Printf("a worker is doning hard job:[%d]\n",jobId)time.Sleep(time.Second*time.Duration(rand.Int31()%10))select{case<-ctx.Done():casech<-jobId:}}
hardJOb和hardJobWithContext的唯一区别就是签名里包含了ctx.Context,并且用上select处理两个channel, 要是超时了,goroutine就会从 case <- ctx.Done() 这里返回,从而释放资源。
funcTestMain(m*testing.M){m.Run()isLeaked:=CheckLeakedGoroutine()ifranSample&&!isLeaked{fmt.Fprintln(os.Stderr,"expected leaky goroutines but none is detected")os.Exit(1)}os.Exit(0)}
funcCheckLeakedGoroutine()bool{gs:=interestingGoroutines()iflen(gs)==0{returnfalse}stackCount:=make(map[string]int)re:=regexp.MustCompile(`\(0[0-9a-fx, ]*\)`)for_,g:=rangegs{// strip out pointer arguments in first function of stack dump
normalized:=string(re.ReplaceAll([]byte(g),[]byte("(...)")))stackCount[normalized]++}fmt.Fprintf(os.Stderr,"Unexpected goroutines running after all test(s).\n")forstack,count:=rangestackCount{fmt.Fprintf(os.Stderr,"%d instances of:\n%s\n",count,stack)}returntrue}