1. golang context 有什么作用?
context可用于指定超时时长取消多个goroutine (使用WithCancel方法),
指定触发条件取消多个goroutine (使用WithTimeout方法).
还可以设置key,value (使用WithValue, Value方法).
2. context.Background() 是干嘛的?
用于new(emptyCtx),作为初始节点, emptyCtx属于int类型, 但实现了context.Context接口的所有方法, 故初始化了Context, 方便以后初始化cancelCtx, timerCtx.
3. WithCancel() 和 WithTimeout() 可以通知多个goroutine, 如何实现的?
调用cancel(), close c.done channel, 从而close 单项接收管道,返回空结构体, 使select监听的ctx.Done()返回空结构体, 取消阻塞, 结束多个协程, 返回自定义的结果.
close c.done channel
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
c.mu.Lock()
if c.err != nil {
c.mu.Unlock()
return // already canceled
}
c.err = err
if c.done == nil {
c.done = closedchan
} else {
close(c.done)
}
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
if removeFromParent {
removeChild(c.Context, c)
}
}
从而close 这里返回的单项接收管道,使其返回空结构体. 使select 监听的ctx.Done(),返回零值,取消阻塞,退出goroutine.
func (c *cancelCtx) Done() <-chan struct{} {
c.mu.Lock()
if c.done == nil {
c.done = make(chan struct{})
}
d := c.done
c.mu.Unlock()
return d
}
WithCancel() 使用方式:
func TestCancelContextV2(t *testing.T) {
wg := new(sync.WaitGroup)
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
go func1(ctx, wg)
time.Sleep(time.Second * 2)
// 2s后,人为取消
cancel()
// 等待goroutine退出
wg.Wait()
}
func func1(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
respC := make(chan int)
defer close(respC)
// 处理逻辑 无缓冲channel 需要先读取,再写入,不然会死锁,所以加到goroutine里写,先阻塞挂起,让后面的读取先执行.
go func() {
respC <- 10
}()
// 取消机制
for {
select {
case <-ctx.Done(): // 当c.done被关闭后,Done()返回{},取消阻塞,return func, 结束所有goroutine.
fmt.Println("cancel")
return
case r := <-respC:
fmt.Println(r)
}
}
}
WithTimeout() 使用方式:
func TestTimeoutContext(t *testing.T) {
fmt.Println("start TestTimeoutContext")
var wg sync.WaitGroup
messages := make(chan int, 10)
// producer
for i := 0; i < 10; i++ {
messages <- i
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer close(messages)
defer cancel()
// consumer
wg.Add(1)
go func(ctx context.Context) {
// 每隔1s 打印一次message, 直到第5s timeout, 中断所有的goroutine.
ticker := time.NewTicker(1 * time.Second)
for _ = range ticker.C {
select {
case <-ctx.Done():
wg.Done()
fmt.Println("child process interrupt...")
return
default:
fmt.Printf("send message: %d\n", <-messages)
}
}
}(ctx)
wg.Wait()
}