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()
}