本文介绍Golang中Context包的实现,内容如下:
- Context包介绍
- Context的功能
- Context的应用
- Context的获取
- Context取消的实现
一、Context包介绍
1、Context包的内容是定义Context类型,该类型可以在调用goroutine过程中携带截止日期、取消信号以及值。
2、Context派生了WithCancel、WithDeadline、WithTimeout函数取消操作,参数带入父Context,返回一个子Context和CancelFunc(取消函数)。CancelFunc的功能取消子Context和属于他的子Context,从父Context引用中移除,停止相关的计时器,调用CancelFunc失败会泄露子Context和属于他的子Context,直到父Context被取消或者定时器被触发,go vet 工具检测CacelFuncs是否被使用。
3、不要将Context存储在Struct类型中,正确的使用方法是第一个参数显示的传递Context。
func DoSomething(ctx context.Context, arg Arg) error {
// ... use ctx ...
}
4、Context对于多个协程来说是安全的。
二、Context的功能
Goroutine并发编程中用于超时、异常、撤销操作等,需要取消后续操作。
Package context接口代码预览
Variables
var Canceled = errors.New("context canceled")
var DeadlineExceeded error = deadlineExceededError{}
type CancelFunc
type Context
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
func Background() Context
func TODO() Context
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接口注解
从上代码结构看type Context类代码中有四个方法分别是:
Deadline() 返回Context绑定的截止时间,没有设置时间值ok为false;
Done() 返回Context任务是否被取消的channel,取消返回一个关闭的channel,不会被取消返回nil;
Err() 如果Done返回的channel没有关闭,将返回nil;如果Done返回的channel已经关闭,将返回非空的值表示任务结束的原因。如果是context被取消,Err将返回Canceled;如果是context超时,Err将返回DeadlineExceede;
Value() 返回context存储的键值对中当前key对应的值,如果没有对应的key,则返回nil。
三、Context的应用
如下代码是对Context的一个简单的应用,如何更优雅实现协程间取消信号的同步,从下代码可以看到使用了连个Context方法:
- context.Background() //获取Context
- context.WithTimeout()// WithTimeout实现Context
下文更多内容会介绍Context的获取和实现。
func main() {
messages := make(chan int, 10)
// producer
for i := 0; i < 10; i++ {
messages <- i
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
// consumer
go func(ctx context.Context) {
ticker := time.NewTicker(1 * time.Second)
for _ = range ticker.C {
select {
case <-ctx.Done():
fmt.Println("child process interrupt...")
return
default:
fmt.Printf("send message: %d\n", <-messages)
}
}
}(ctx)
defer close(messages)
defer cancel()
select {
case <-ctx.Done():
time.Sleep(1 * time.Second)
fmt.Println("main process exit!")
}
}
四、Context的获取
在Context使用过程中,获取当前的Context会调用context的实现类型emptyCtx,我们通常是调用Background()或者TODO();一般在Main中初始化Context的时候会使用Background,在不确定使用什么Context的时候采用TODO()。
emptyCtx类型介绍
emptyCtx没有超时时间,不能取消,也不能存储任何额外信息,所以emptyCtx用来作为context树的根节点。
emptyCtx代码预览:
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
// struct{}, since vars of this type must have distinct addresses.
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 interface{}) interface{} {
return nil
}
func (e *emptyCtx) String() string {
switch e {
case background:
return "context.Background"
case todo:
return "context.TODO"
}
return "unknown empty Context"
}
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
五、Context取消的实现
3个方法实现取消Context:
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)
分别是截止时间的和不带时间的,实现上面三个方法需要使用 cancelCtx 和基于cancelCtx类型的 timerCtx 类型。
cancelCtx类型: 该类型嵌入了Context,自己实现了done,children存储了当前context节点下的子节点等。
cancelCtx类型代码
Variables
var Canceled = errors.New("context canceled")
var DeadlineExceeded error = deadlineExceededError{}
type CancelFunc
type Context
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
func Background() Context
func TODO() Context
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
1、WithCancel方法实现Context:
返回CancelCtx实例并实现propagateCancel方法的功能。
propagateCancel方法的功能:
- 检查parent是否可以取消
- 检查parent是否是cancelCtx类型,如果是再检查是否已经cancel掉,是则cancel掉child,否则加入child,如果不是开启一个协程,监听parent.Done()和child.Done(),一旦parent.Done()返回的channel关闭,即context链中某个祖先节点context被取消,则将当前context也取消。
代码预览
func main() {
messages := make(chan int, 10)
// producer
for i := 0; i < 10; i++ {
messages <- i
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
// consumer
go func(ctx context.Context) {
ticker := time.NewTicker(1 * time.Second)
for _ = range ticker.C {
select {
case <-ctx.Done():
fmt.Println("child process interrupt...")
return
default:
fmt.Printf("send message: %d\n", <-messages)
}
}
}(ctx)
defer close(messages)
defer cancel()
select {
case <-ctx.Done():
time.Sleep(1 * time.Second)
fmt.Println("main process exit!")
}
}
2、以cancelCtx为基础实现timerCtx实现WithDeadline和WithTimeout的定时取消Context:
timerCtx代码预览
func main() {
messages := make(chan int, 10)
// producer
for i := 0; i < 10; i++ {
messages <- i
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
// consumer
go func(ctx context.Context) {
ticker := time.NewTicker(1 * time.Second)
for _ = range ticker.C {
select {
case <-ctx.Done():
fmt.Println("child process interrupt...")
return
default:
fmt.Printf("send message: %d\n", <-messages)
}
}
}(ctx)
defer close(messages)
defer cancel()
select {
case <-ctx.Done():
time.Sleep(1 * time.Second)
fmt.Println("main process exit!")
}
}
WithDeadline实现Context代码:
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
// 建立新建context与可取消context祖先节点的取消关联关系
propagateCancel(parent, c)
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func() { c.cancel(false, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
WithDeadline实现Context代码:
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return
WithDeadline(parent, time.Now().Add(timeout))
}
WithValue在Context中的代码实现:
type valueCtx struct {
Context
key, val interface{}
}
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}
func WithValue(parent Context, key, val interface{}) Context {
if key == nil {
panic("nil key")
}
if !reflect.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}