package main
import (
"context"
"fmt"
"math/rand"
"os"
"os/signal"
"sync"
"syscall"
"time"
)
func doFunc1(ctx context.Context, prompt string, wg *sync.WaitGroup) {
defer func() {
<-time.After(time.Duration(rand.Intn(5000)) * time.Millisecond)
fmt.Printf("%v is leaving.\n", prompt)
wg.Done()
}()
if "uno" == prompt {
wg.Add(1)
newCtx, _ := context.WithCancel(ctx)
go doFunc1(newCtx, "tres", wg)
}
intv := time.NewTicker(time.Second + time.Duration(rand.Intn(1000))*time.Millisecond)
for {
select {
case <-intv.C:
fmt.Printf("<%v>tick tick\n", prompt)
case <-ctx.Done():
return
default:
}
}
}
func main() {
rand.Seed(time.Now().UnixNano())
defer fmt.Println("bye")
ctx, doCancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
wg.Add(2)
go doFunc1(ctx, "uno", &wg)
go doFunc1(ctx, "dos", &wg)
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Kill, os.Interrupt, syscall.SIGTERM)
<-sigCh
fmt.Println("closing")
doCancel()
timeoutCtx, _ := context.WithTimeout(context.Background(), 3*time.Second)
doneCh := make(chan struct{}, 1)
go func() {
defer fmt.Println("waited 2000.")
wg.Wait()
close(doneCh)
}()
select {
case <-doneCh:
fmt.Printf("waited done.\n")
case <-timeoutCtx.Done():
fmt.Printf("done error: %v\n", timeoutCtx.Err())
}
}
- 带“嵌套”的go呼叫. context的cancel行为是带传递性的transitive
- 就算有呼叫者的主动cancel, 也需要子过程主动去查询“自己是否被cancel掉”
- 如果使用sync.WaitGroup的Wait, 可能存在永远的等待.
源码分析: (1.9.2)
Done()完成之后Err()的返回值属于以下两个:
// Canceled is the error returned by Context.Err when the context is canceled.
var Canceled = errors.New("context canceled")
// DeadlineExceeded is the error returned by Context.Err when the context's
// deadline passes.
var DeadlineExceeded error = deadlineExceededError{}
type deadlineExceededError struct{}
注: error在golang中是一个接口:仅需要实现Error()string即可.
// propagateCancel arranges for child to be canceled when parent is.
func propagateCancel(parent Context, child canceler) {
if parent.Done() == nil {
return // parent is never canceled
}
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
if p.err != nil {
// parent has already been canceled
child.cancel(false, p.err)
} else {
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
建立context对象初期就会去创建一个goroutine, 用select挂在parent和自己的done channel上。
// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}
WithCancel除了创建对象也要返回canceler, canceler就是对 cancelContext对象上调用cancel方法:
cancel之后总是会标记error, 所以canceler可以重复调用。