Go - context包的使用

context简介

  • context用于在多个goroutine中传递上下文信息,且相同的context传递给运行在不同goroutine中的函数是并发安全的。
  • context包定义的上下文类型,可以使用backgroundTODO创建一个上下文。也可以使用WithDeadlineWithTimeoutWithCancelWithValue创建的修改副本替换它。

因为context具有在多个不同goroutine中传递上下文信息的性质,所以其常用于并发控制。

下面是对context包的详细使用:

context的使用

创建一个context

context包提供了两种创建方式:

  • context.Background()
  • context.TODO()
  • 前者是上下文的默认值,所有其他的上下文一般都从它衍生而来。
  • 后者是在不确定使用哪种上下文时使用。
    所以,大多数情况下,都使用context.Background()来作为上下文进行传递。

这两种方式创建出来的是根context,不具有任何功能。需要根据实际选择context包提供的With系列函数来解决相应的问题。

下面是context包提供的With系列函数:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context

context的衍生具有树形结构的特点。

  • 这四个函数所返回的context都是基于父级context所衍生的。而其返回的context依然可以作为父节点,衍生出其他子节点。
  • 如果一个context节点被取消,那么从其所衍生出来的context子节点都会被取消。

下面是对这些With函数的具体使用介绍:

With系列函数

WithCancel 取消控制

WithCancel的作用是,我们可以通过传递这样的上下文,去控制多个goroutine,通过cancel函数,在任意时刻让这些goroutine取消。

下面是例子:

func main() {  
    ctx, cancel := context.WithCancel(context.Background())  
    go task(ctx)  
    time.Sleep(5 * time.Second)  
    cancel()  
    time.Sleep(time.Second)  
}  
  
func task(ctx context.Context) {  
    for range time.Tick(time.Second) {  
       select {  
       case <-ctx.Done():  
          fmt.Println(ctx.Err())  
          return  
       default:  
          fmt.Println("tasking...")  
       }    
    }
}

超时控制

一个健壮的程序都是需要设置超时时间的,避免由于服务端长时间响应而消耗资源。所以,一些web框架都会采用WithDeadlineWithTimeOut函数来做超时控制。

  • WithDeadlineWithTimeout的作用是一样的,只是传递的时间参数不同而已,它们都会在超过传递的时候后,自动取消context
  • 需要注意的是,两者也会返回CancelFunc的函数,即便是被自动取消,也需要在结束后,手动取消一下,避免消耗不必要的资源。

下面是例子:

func main() {  
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)  
    defer cancel()  
    go task(ctx)  
    time.Sleep(6 * time.Second)  
}  
  
func task(ctx context.Context) {  
    for range time.Tick(time.Second) {  
       select {  
       case <-ctx.Done():  
          fmt.Println(ctx.Err())  
          return  
       default:  
          fmt.Println("tasking...")  
       }    
    }
}

WithValue 携带数据

WithValue函数可以返回一个可携带数据的context,可以用于在多个goroutine进行传递。
例如,在日常业务开发中,需要有一个trace_id来串联所有日志,那么就可以使用WithValue来实现。

需要注意的是,通过WithValue得到的context所携带的数据,是可以传递给从其衍生出来的子节点。简单来说,该context的整棵子树都会携带这个数据。

下面是例子:

func main() {  
    ctx := context.WithValue(context.Background(), "key", "value")  
    go task(ctx)  
    time.Sleep(time.Second)  
}  
  
func task(ctx context.Context) {  
    fmt.Println(ctx.Value("key"))  
}

使用WithVlue的注意事项:

  • 不建议使用context携带关键参数,关键参数应该显示的声明出来,而不是隐式处理。context最好是携带签名、trace_id这类值。
  • 建议key采用内置类型。这是为了避免context因多个包同时使用context而带来冲突。
  • 在获取value时,context会首先从当前ctx中获取,如果没有找到,则会从父级继续查找,直到查找到或者在某个父context中返回nil
  • context传递的key``value键值对是interface类型,因此在类型断言时,要考虑程序的健壮性。

总结

context包在做并发控制上具有相当方便的功能,如在做任务的取消、超时以及传递隐式参数的情境。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值