【Golang】标准库中的context基础介绍

文章目录

  • 作用
  • context.Context
    • 理解
    • 4个接口方法
    • 4个具体的Context类型和API
    • Context的层级关系
      • 示例
      • 工作方式
    • 其它细节和注意事项

作用

在goroutine调用树中传递通知或者数据。context.Context可在多个goroutine之间进行传递,相比于channel,它规定了接口,更加方便使用,规范性更强。

  • 退出通知:通知可以传递给goroutine调用树上的每个goroutine,使得在多级goroutine调用链上取消动作更加容易,可以方便地取消调用链上的任何一个goroutine。

    需要注意的是,还是需要我们自己手动处理Done()的返回值来决定是否退出goroutine,context只是负责传递信息,但是停止goroutine的行为还是要自己处理。

  • 传递数据:数据可以传递给goroutine调用树上的每个goroutine

context.Context

理解

  1. context.Context是一个接口类型,是go标准库中为用户提供的使用context的统一格式类型;
  2. 需要使用Context的时候,就使用context.Context接口类型,一般在函数的第一个参数中传入context.Context

4个接口方法

context.Context接口的全部方法体现了其作用:传递通知和传递数据,4个接口方法定义如下:

// 传递退出通知
// 当Context被取消,Done()函数返回的是一个已经关闭的channel,从而可以使得多个goroutine得到通知。无论是主动调用CancelFunc还是Context设定的定时时间到期,都会通知退出。
func Done()chan struct{}
// 传递数据
// 通过key-value的形式传递数据,一个Context只能传递一对key-value
func Value(any) any
// 获取Context退出的原因
func Err() error
// 获取Context是否设置了最后期限,如果设置了,则bool返回true,并且返回最后期限的时间;否则返回bool为false
func Deadline() (time.Time, bool)

4个具体的Context类型和API

  1. emptyCtx
func Background() Context	
func TODO() Context
  1. cancelCtx
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
  1. timerCtx
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) 
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
  1. valueCtx
func WithValue(parent Context, key, val any) Context

Context的层级关系

一个Context可以作为其它Context的父Context,只需要在使用API创建Context的时候指定。

示例

// parent context
// ctx_parent -> ctx_level1 -> ctx_level2
ctx_parent, cancel := context.WithCancel(context.Background())

// level1 context
// 6s后过期
ctx_level1, _ := context.WithTimeout(ctx_parent, time.Second * 6)

// level2 context
// level1过期的话,level2也会跟着过期
ctx_level2, _ := context.WithCancel(ctx_level1)

var wg sync.WaitGroup

f := func(ctx context.Context, level int, interval int) {
	defer wg.Done()
	for {
		select {
		case <-ctx.Done():
			fmt.Printf("level-%d received done\n", level)
			return
		default:
			fmt.Printf("level-%d running\n", level)
			time.Sleep(time.Duration(interval) * time.Second)
		}
	}
}

wg.Add(2)

go f(ctx_parent, 0, 1)
go f(ctx_level1, 1, 2)
go f(ctx_level2, 2, 1)

wg.Wait()

wg.Add(1)
cancel()
wg.Wait()

工作方式

  1. 通知退出

    父Context的退出通知会向下传递到子Context

    但是子Context的退出通知则不会向上传递到父Context

  2. 值传递

    子Context可以找到父Context中的key-value

    但是父Context找不到子Context中的key-value,示例如下

    root := context.Background()
    ctx_parent := context.WithValue(root, keyType("parent"), "parent-value")
    ctx_child := context.WithValue(ctx_parent, keyType("child"), "child-value")
    
    var wg sync.WaitGroup
    type keyType string
    // WithValue需要使用自定义的key类型,防止和使用context的库造成冲突
    f := func(ctx context.Context, name string, keyname keyType) {
    	defer wg.Done()
    	fmt.Printf("[goroutine %s] ctx[%v]=%v\n", name, keyname, ctx.Value(keyname))
    }
    
    wg.Add(2)
    go f(ctx_parent, "goroutine-1", keyType("child"))	// 拿不到 key为child的值
    go f(ctx_child, "goroutine-2", keyType("parent"))	// 可以拿到key为parent的值
    wg.Wait()
    

其它细节和注意事项

Go标准库contex文档

  1. 不要将context.Context作为一个struct的成员,而是应该通过函数传参的方式传递,通常Context都放在第一个参数,名字通常命名为ctx
  2. 不要传递值为nil的Context,应该要用context.TODO()代替
  3. context的Value不应该用来传递一些和业务相关的参数(not for passing optional parameters to functions.)
  4. Context是并发安全的(Contexts are safe for simultaneous use by multiple goroutines)
  5. WithDeadline(parent context.Context, d time.Time),如果parent也设置有deadline,并且parent的deadline已经比d要早了,那么WithDeadline返回的Context的deadline时间以parent为准
  6. CancelFunc类型,这个函数类型是With系列函数的第二个返回值(除WithValue),可以直接调用这个函数,用来广播退出通知。
    在不同的goroutine中同时调用CancelFunc是并发安全的,如果同时调用的话,首次调用后的后续调用不影响。(A CancelFunc may be called by multiple goroutines simultaneously. After the first call, subsequent calls to a CancelFunc do nothing.)

你可能感兴趣的:(Golang,golang)