context 在 http 服务, rpc 服务等功能中经常被用到。
在 golang 并发编程中,channel 被用来在 goroutine 之间共享数据, 而 context 主要被用来保存一个 goroutine 和它创建的 goroutine 之间的上下文管理。可以用来传递变量和提供超时等功能。
context 的接口定义
type Context interface{
Done() <- chan struct{}
Err() error
Deadline()(deadline time.Time, ok bool)
Value(key interface{}) interface{}
}
该接口定义了四个函数来观察 context 的状态
- Done 返回一个当 context 被取消或者结束时候会被关闭的 channel
- Deadline 如果 context 被设置了过期时间,者返回该时间 如果没有定义该过期, 返回false
- Err 当 Done 返回的 channel 关闭后,Err 返回 non-nil 的 错误值。如果被取消返回 Canceled , 如果过期返回 DeadlineExceeded
- Value 返回和 context 相关联的值
默认 context
context 包中提供了两个默认的 package
var (
todo = context.TODO()
background = context.Background()
)
- context.TODO 当不清楚该使用哪一个 context 的时候,可以使用 context.TODO
- BackGround 通常在初始化函数、main 函数等高层的的地方被使用
两个 context 都没有取消、没有传值,也没有过期功能
其他 context
context包提供了四种基础的 context
context.WithCancel 返回一个 从 parent context 继承的 context。 如果 parent context 的 Done 返回的channel 别关闭或 CancelFunc 被调用的时候,该 context 的 Done 返回的 channel 也会被关闭。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { ctx, f := context.WithCancel(parent) return ctx, CancelFunc(f) }
context.WithTimeout 返回一个 context。 当该 context 的 CancelFunc 被调用,或者 context 到期,或者 parent context 的 Done Channel 关闭,都会导致该 context 的Done channel 关闭
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { return WithDeadline(parent, time.Now().Add(timeout)) }
context.WithDeadline 和 WithTimeout 功能类似
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { ctx, f := context.WithDeadline(parent, deadline) return ctx, CancelFunc(f) }
context.WithValue 返回的 context 有一个 key-value map,该 context 及其子 goutine 都能使用该 map
func WithValue(parent Context, key interface{}, val interface{}) Context { return context.WithValue(parent, key, val) }
example
下面的一个例子中包含了 context 基本用法
package main
import (
"bufio"
"fmt"
"strings"
"time"
"context"
)
// ctx 一般用作第一个参数
func Inc(ctx context.Context, a int) {
// 此处用 ctx 的 value 来获取 一个值。 不过 ctx 的 value 通常情况下不用来传递参数。
// 此处只是用来说明 context.Value 的用法
intival := ctx.Value("interval").(time.Duration)
for {
select {
// 当子 ctx 的 cancelfunc 被调用的时候或者 context 到期, Done 关闭
case <-ctx.Done():
if ctx.Err() == context.Canceled { // ctx 的 cancel 被调用
fmt.Println("function canceled")
return
} else if ctx.Err() == context.DeadlineExceeded {
// WithDeadline 或 WithTimeout 设定的超时被触发
fmt.Println("function time out ")
return
}
default:
time.Sleep(intival)
a++
}
}
}
func main() {
dura := time.Second * 1
ctx := context.WithValue(context.Background(), "interval", dura)
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
// 先从 Background context 继承了一个带 value 的 context,再继承了一个带超时的 ctx。
go Inc(ctx, 0) // 新开一个 goutine, 传入当前 goroutine 的上下文
// context 是 goroutine 安全的, 可以在多个 goroutine 中同时访问该 context
fmt.Print("Inc function is runnning")
if deadline, ok := ctx.Deadline(); ok {
fmt.Printf(" %s second left to run the Inc function .\n", deadline.Sub(time.Now()).String())
}
// ctx.Deadline 主要获取ctx 的过期时间,如果没有设置超时的话 ok 会返回 false
reader := bufio.NewReader(os.Stdin)
for {
fmt.Printf(" Press A to abort: ")
if line, err := reader.ReadString('\n'); err == nil {
command := strings.TrimSuffix(line, "\n")
switch command {
case "A":
cancel() // 手动调用 ctx 的cancelfunc ctx Done 返回的 channel 会关闭
time.Sleep(500 * time.Millisecond)
return
default:
if deadline, ok := ctx.Deadline(); ok {
fmt.Printf("%s second left, Press A to stop `\n", deadline.Sub(time.Now()).String())
}
}
}
}
}
总结
遇到 context 了比较长一段时间了,之前一直看不明白。 这次用下心来发现也没有那么困难, 先尝试去理解,理解知乎去试着写一个 demo。 这样理解就会加深很多。然后,现在看的代码是在太少了,继续加油吧。