您的位置 首页 golang

Go Context 的深入理解

本文介绍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}
}  

文章来源:智云一二三科技

文章标题:Go Context 的深入理解

文章地址:https://www.zhihuclub.com/99003.shtml

关于作者: 智云科技

热门文章

网站地图