Go语言Context如何使用

本文最后更新于 2024年10月15日 下午

Go语言Context如何使用

概述

相信大家在日常使用Go语言进行开发的时候一定会遇到下面这种代码,特别是在Web开发中使用了诸如Gin、Echo和Beego类似的开发框架。

1
2
3
4
5
6
7
func getUser(ctx context.Context, id int64){
getUserById(ctx, id)
}

func getUserById(ctx context.Context, id int64){
...
}

能够从上面的代码中看出,每个方法的第一个参数都是Context
Context是Go语言在1.7版本引入的,Context可以用来在goroutine之间传递上下文信息,通常可以用来进行超时控制,消息传递等,Go语言官方建议的将Context作为函数的第一个参数并不断地透传下去以实现在不同的goroutine之间传递上下文。

Context的使用

context包中提供了两种方式创建Context

  • context.Backgroud()
  • context.TODO()

通过这两种方式都可以创建一个Context,而且两者之间没有区别,官方给出的定义是:

  • context.Background 是上下文的默认值,所有其他的上下文都应该从它衍生(Derived)出来;
  • context.TODO 应该只在不确定应该使用哪种上下文时使用;

使用这两种方法创建出来的Context都是默认的Context,也可以称为父Context,不具备我们上面说到的消息传递,超时控制这些功能,需要实现这些功能就需要使用context包提供的另外几个创建Context的方法:

  • context.WithCancel(parent Context)
  • context.WithDeadline(parent Context, deadline time.Time)
  • context.WithTimeout(parent Context, timeout time.Duration)
  • context.WithValue(parent Context, key, val interface{})

传递数据

在日常的开发中,日志是必不可少的一部分,同时我们都希望在日志打印的时候能够携带一个tranceID,这样的话根据一个tranceID就可以关联出一个请求的所有日志,这个在Go语言中就可以使用Context的传递数据方式实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const Key = "trance_id"

func main() {
ctx := context.WithValue(context.Background(), Key, "1001")
router(ctx)
}

func router(ctx context.Context) {
printLog(ctx, "router方法的日志")
service(ctx)
}

func service(ctx context.Context) {
printLog(ctx, "service方法的日志")
}

func printLog(ctx context.Context, msg string) {
fmt.Printf("%s | level=info | trance_id=%s | message=%s\n", time.Now().Format("2006-01-02 15:04:05"), ctx.Value(Key), msg)
}

上面的代码中通过WithValue方法创建了一个可以携带数据的Context,然后在方法调用链中将Context作为第一个参数一直传递,在每个方法中都可以通过Value取到最开始放进去的tranceID,最后打印结果如下:

1
2
2024-07-18 19:44:24 | level=info | trance_id=1001 | message=router方法的日志
2024-07-18 19:44:24 | level=info | trance_id=1001 | message=service方法的日志

超时控制

要使用Context实现超时控制可以使用WithTimeoutWithDeadline,通过这两个方法创建出来的Context都可以实现超时控制,两个方法的作用是一样的。同时两个方法都会返回一个cancel方法,通过调用这个方法可以提前取消,不需要等到传入的时间达到即可取消。
示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
ctx, cancelFunc := context.WithTimeout(context.Background(), 3*time.Second)
defer cancelFunc()
test(ctx)
}

func test(ctx context.Context) {
for i := 0; i < 10; i++ {
time.Sleep(1 * time.Second)
select {
case <-ctx.Done():
fmt.Println(ctx.Err())
return
default:
fmt.Printf("test = %d\n", i)
}
}
}

上面的代码中通过WithTimeout创建了一个时间为3秒的Context,然后在test方法中进行循环,每隔1秒输出一个test语句,但是由于Context的超时设置的3秒,所以当3秒后Context就会自动取消,最后test的输出也就只有2行。

1
2
3
test = 0
test = 1
context deadline exceeded

WithCancel取消控制

在开发中有时候可能需要同时创建多个goroutine并行的去进行一些逻辑的处理,但是当主goroutine出现错误的时候,这时候我们就已经不需要其他的goroutine继续执行,这种情况我们就可以使用WithCancel创建一个可以取消的Context,将Context传入到不同的goroutine中,当主goroutine出错时,直接调用cancel方法就可以取消所有的goroutine
示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main() {
ctx, cancelFunc := context.WithCancel(context.Background())
go test(ctx)
time.Sleep(3 * time.Second)
cancelFunc()
time.Sleep(time.Second)
}

func test(ctx context.Context) {
for i := 0; i < 10; i++ {
time.Sleep(1 * time.Second)
select {
case <-ctx.Done():
fmt.Println("取消执行")
return
default:
fmt.Printf("test = %d\n", i)
}
}
}

上面的代码通过WithCancel创建了一个可以取消的Context,然后创建了一个goroutine执行test方法,当主goroutine等待3秒后调用cancel方法取消,这时候执行test方法的goroutine就会直接退出不再执行。

1
2
3
test = 0
test = 1
取消执行

Go语言Context如何使用
http://example.com/p/5226b0cc.html
作者
jrlee
发布于
2024年8月19日
许可协议