Skip to content
On this page

Context

概念

  • Context本质上是一个接口
Go
type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key any) any
}
  • Done()函数返回一个只读管道,且管道里不存放任何元素(struct{}),用这个管道就是为了实现阻塞

  • Deadline()用来记录到期时间,以及是否到期

  • Err()用来记录Done()管道关闭的原因,比如可能是因为超时,也可能是因为被强行Cancel

  • Value()用来返回存储的键值对

新建Context

  • 创建一个新的context可以使用context.TODO或者context.Background方法

TODO/Background方法如下,这两个方法返回值一样

Go
var (
	background = new(emptyCtx)
	todo       = new(emptyCtx)
)
func Background() Context {
	return background
}
func TODO() Context {
	return todo
}

emptyCtx实现如下

Go
type emptyCtx int

func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
	return
}

func (*emptyCtx) Done() <-chan struct{} {
	return nil
}

func (*emptyCtx) Err() error {
	return nil
}

func (*emptyCtx) Value(key any) any {
	return nil
}

func (e *emptyCtx) String() string {
	switch e {
	case background:
		return "context.Background"
	case todo:
		return "context.TODO"
	}
	return "unknown empty Context"
}

WithTimeout

  • 使用context.Withtimeout创建一个context,当超过指定的时间或者调用cancel()时会关闭context.Done()管道

  • 超过设定的时长后会自动调用contextDeadline方法,调用Deadline方法会触发Done

Go
func withTimeoutFn() {
	parentCtx := context.TODO()
	child, cancel := context.WithTimeout(parentCtx, 500 * time.Millisecond)
	defer cancel()
	select {
	case <-child.Done():
		err := child.Err()
		fmt.Printf("结束了,原因是%s", err)
	case <-time.After(300 * time.Millisecond):
		fmt.Println("超过300ms")
	}
}
// 超过300ms

上述代码由于设置了Withtimeout500ms后才会超时,所以会打印超过300ms

Go
func withTimeoutFn() {
	parentCtx := context.TODO()
	child, cancel := context.WithTimeout(parentCtx, 500 * time.Millisecond)
	defer cancel()
	select {
	case <-child.Done():
		err := child.Err()
		fmt.Printf("结束了,原因是%s", err)
	case <-time.After(600 * time.Millisecond):
		fmt.Println("超过600ms")
	}
}
// 结束了,原因是context deadline exceeded

上述代码会捕获到context的超时

Timeout的继承问题

  • 如果父context的超时时间为500ms,而子context的超时时间为300ms,那么这个context会在300ms的时候就触发超时结束

如下父context设置了500ms超时,中间睡眠了300ms,然后生成一个子context100ms后超时,那么实际上这个context400ms的时候就超时了

Go
func timeoutInherit() {
	parentCtx, cancel1 := context.WithTimeout(context.TODO(), 500 * time.Millisecond) // 500ms 后会超时
	t0 := time.Now()
	defer cancel1()
	time.Sleep(300 * time.Millisecond) // 睡眠300ms
	childCtx, cancel2 := context.WithTimeout(parentCtx, 100 * time.Millisecond) // 100ms后会超时
	t1 := time.Now()
	defer cancel2()
	select {
	case <-parentCtx.Done():
		t2 := time.Now()
		err := parentCtx.Err()
		fmt.Printf("parentCtx超时,原因为:%v,parentCtx存留时间为%v, childCtx存留时间为%v", err, t2.Sub(t0), t2.Sub(t1))
	case <-childCtx.Done():
		t2 := time.Now()
		err := childCtx.Err()
		fmt.Printf("childCtx超时,原因为:%v,parentCtx存留时间为%v, childCtx存留时间为%v", err, t2.Sub(t0), t2.Sub(t1))
	}
}
// childCtx超时,原因为:context deadline exceeded,parentCtx存留时间为402.156625ms, childCtx存留时间为101.098041ms

WithCancel

  • 使用context.WithCancel创建一个context,调用cancel()时会关闭context.Done()管道
Go
func withCancelFn() {
    parentCtx := context.Background()
    childCtx, cancel := context.WithCancel(parentCtx)
	go func ()  {
		time.Sleep(300 * time.Millisecond)
		cancel()
	}()
	select {
	case <-childCtx.Done():
		err := childCtx.Err()
		fmt.Printf("结束了,原因是%s", err)	
	case <-time.After(500 * time.Millisecond):
		fmt.Println("超过500ms")	
	}
}
// 结束了,原因是context canceled

WithValue

  • 可以使用WithValuecontext中存储一些数据
Go
func withValueFn() {
	parentCtx := context.TODO()
	childCtx := context.WithValue(parentCtx, "name", "南山")
	name := childCtx.Value("name")
	fmt.Printf("获取到的name为%s", name)
}
// 获取到的name为南山
  • context中存储的数据具有继承性

childCtx中存储的数据可以通过child1Ctx来获取

Go
func addAge(ctx context.Context) context.Context {
	childCtx := context.WithValue(ctx, "age", 22)
	return childCtx
}

func addLike(ctx context.Context) context.Context {
	childCtx := context.WithValue(ctx, "like", []string{"篮球","乒乓球"})
	return childCtx;
}
func withValueInherit() {
	parentCtx := context.TODO()
	childCtx := context.WithValue(parentCtx, "name", "南山")
	child1Ctx := addAge(childCtx)
	fmt.Printf("child1的name为%s,age为%d\n", child1Ctx.Value("name"), child1Ctx.Value("age"))
	child2Ctx := addLike(child1Ctx)
	fmt.Printf("child2的name为%s,age为%d, like为%v\n", child2Ctx.Value("name"), child2Ctx.Value("age"),  child2Ctx.Value("like"))
}
/**
child1的name为南山,age为22
child2的name为南山,age为22, like为[篮球 乒乓球]
*/