文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Go中的Context怎么使用

2023-07-06 05:12

关注

这篇“Go中的Context怎么使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Go中的Context怎么使用”文章吧。

1、Context定义

Context 接口定义如下

type Context interface {  // Deadline returns the time when this Context will be canceled, if any.Deadline() (deadline time.Time, ok bool)  // Done returns a channel that is closed when this Context is canceled  // or times out.Done() <-chan struct{}  // Err indicates why this context was canceled, after the Done channel  // is closed.Err() error  // Value returns the value associated with key or nil if none.Value(key any) any}

Deadline(): 返回的第一个值是 截止时间,到了这个时间点,Context 会自动触发 Cancel 动作。返回的第二个值是 一个布尔值,true 表示设置了截止时间,false 表示没有设置截止时间,如果没有设置截止时间,就要手动调用 cancel 函数取消 Context。

Done(): 返回一个只读的通道(只有在被cancel后才会返回),类型为 struct{}。当这个通道可读时,意味着parent context已经发起了取消请求,根据这个信号,开发者就可以做一些清理动作,退出goroutine。这里就简称信号通道吧!

Err():返回Context 被取消的原因

Value: 从Context中获取与Key关联的值,如果没有就返回nil

2、Context的派生

2.1、创建Context对象

context包提供了四种方法来创建context对象,具体方法如下:

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

由以上方法可知:新的context对象都是基于父context对象衍生的.

简单的树状关系如下(实际可衍生很多中):

Go中的Context怎么使用

2.2、parent Context

context包默认提供了两个根context 对象backgroundtodo;看实现两者都是由emptyCtx创建的,两者的区别主要在语义上,

var (background = new(emptyCtx)todo       = new(emptyCtx))// Background 创建background contextfunc Background() Context {return background}// TODO 创建todo contextfunc TODO() Context {return todo}

3、context 接口四种实现

Go中的Context怎么使用

具体结构如下,我们大致看下相关结构体中包含的字段,具体字段的含义及作用将在下面分析中会提及。

type emptyCtx int // 空context
type cancelCtx struct {Context // 父contextmu       sync.Mutex            // protects following fieldsdone     atomic.Value          // of chan struct{}, created lazily, closed by first cancel callchildren map[canceler]struct{} // set to nil by the first cancel callerr      error                 // cancel的原因}
type timerCtx struct {cancelCtx //父contexttimer *time.Timer // 定时器deadline time.Time // 截止时间}
type valueCtx struct {Context // 父context  key, val any // kv键值对}

4、 emptyCtx 源码分析

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 intfunc (*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 any) any {return nil}func (e *emptyCtx) String() string {switch e {case background:return "context.Background"case todo:return "context.TODO"}return "unknown empty Context"}

5、 cancelCtx 源码分析

cancelCtx 的实现相对复杂点,比如下面要介绍的timeCtx 底层也依赖它,所以弄懂cancelCtx的工作原理就能很好的理解context.

cancelCtx 不仅实现了Context接口也实现了canceler接口

5.1、对象创建withCancel()

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {if parent == nil { // 参数校验panic("cannot create context from nil parent")}  // cancelCtx 初始化c := newCancelCtx(parent)propagateCancel(parent, &c) // cancelCtx 父子关系维护及传播取消信号return &c, func() { c.cancel(true, Canceled) } // 返回cancelCtx对象及cannel方法}// newCancelCtx returns an initialized cancelCtx.func newCancelCtx(parent Context) cancelCtx {return cancelCtx{Context: parent}}

用户调用WithCancel方法,传入一个父 Context(这通常是一个 background,作为根节点),返回新建的 context,并通过闭包的形式返回了一个 cancel 方法。如果想要取消context时需手动调用cancel方法。

5.1.1、newCancelCtx

cancelCtx对象初始化, 其结构如下:

type cancelCtx struct {  // 父 contextContext //  parent context// 锁 并发场景下保护cancelCtx结构中字段属性的设置mu       sync.Mutex            // protects following fields   // done里存储的是信号通道,其创建方式采用的是懒加载的方式done     atomic.Value          // of chan struct{}, created lazily, closed by first cancel call   // 记录与父子cancelCtx对象,children map[canceler]struct{} // set to nil by the first cancel call  // 记录ctx被取消的原因err      error                 // set to non-nil by the first cancel call}
5.1.2、propagateCancel

propagateCancel

// propagateCancel arranges for child to be canceled when parent is.func propagateCancel(parent Context, child canceler) {done := parent.Done() // 获取parent ctx的信号通道 doneif done == nil {  // nil 代表 parent ctx 不是canelctx 类型,不会被取消,直接返回return // parent is never canceled}select { // parent ctx 是cancelCtx类型,判断其是否被取消case <-done:// parent is already canceledchild.cancel(false, parent.Err())returndefault:}  //parentCancelCtx往树的根节点方向找到最近的context是cancelCtx类型的if p, ok := parentCancelCtx(parent); ok { // 查询到p.mu.Lock() // 加锁if p.err != nil { // 祖父 ctx 已经被取消了,则 子cancelCtx 也需要调用cancel 方法来取消// parent has already been canceledchild.cancel(false, p.err)} else { // 使用map结构来维护 将child加入到祖父context中if p.children == nil {p.children = make(map[canceler]struct{})}p.children[child] = struct{}{}}p.mu.Unlock()// 解锁} else { // 开启协程监听 parent Ctx的取消信号 来通知child ctx 取消atomic.AddInt32(&goroutines, +1)go func() {select {case <-parent.Done():child.cancel(false, parent.Err())case <-child.Done():}}()}}
// parentCancelCtx returns the underlying *cancelCtx for parent.// It does this by looking up parent.Value(&cancelCtxKey) to find// the innermost enclosing *cancelCtx and then checking whether// parent.Done() matches that *cancelCtx. (If not, the *cancelCtx// has been wrapped in a custom implementation providing a// different done channel, in which case we should not bypass it.)// parentCancelCtx往树的根节点方向找到最近的context是cancelCtx类型的func parentCancelCtx(parent Context) (*cancelCtx, bool) {done := parent.Done()  // closedchan 代表此时cancelCtx 已取消, nil 代表 ctx不是cancelCtx 类型的且不会被取消if done == closedchan || done == nil { return nil, false}  // 向上遍历查询canelCtx 类型的ctxp, ok := parent.Value(&cancelCtxKey).(*cancelCtx)if !ok { // 没有return nil, false}  // 存在判断信号通道是不是相同pdone, _ := p.done.Load().(chan struct{})if pdone != done {return nil, false}return p, true}

5.2 canceler

cancelCtx也实现了canceler接口,实现可以 取消上下文的功能。

canceler接口定义如下:

// A canceler is a context type that can be canceled directly. The// implementations are *cancelCtx and *timerCtx.type canceler interface {cancel(removeFromParent bool, err error) // 取消Done() <-chan struct{} // 只读通道,简称取消信号通道}

cancelCtx 接口实现如下:

整体逻辑不复杂,逻辑简化如下:

注意

由于信号通道的初始化采用的懒加载方式,所以有未初始化的情况;

已初始化的:调用close 函数关闭channel

未初始化的:用 closedchan 初始化,其closedchan是已经关闭的channel。

// cancel closes c.done, cancels each of c's children, and, if// removeFromParent is true, removes c from its parent's children.func (c *cancelCtx) cancel(removeFromParent bool, err error) {if err == nil {panic("context: internal error: missing cancel error")}c.mu.Lock()if c.err != nil {c.mu.Unlock()return // already canceled}c.err = errd, _ := c.done.Load().(chan struct{})if d == nil {c.done.Store(closedchan)} else {close(d)}for child := range c.children {// NOTE: acquiring the child's lock while holding parent's lock.child.cancel(false, err)}c.children = nilc.mu.Unlock()if removeFromParent {removeChild(c.Context, c)}}
// removeChild removes a context from its parent.func removeChild(parent Context, child canceler) {p, ok := parentCancelCtx(parent)if !ok {return}p.mu.Lock()if p.children != nil {delete(p.children, child)}p.mu.Unlock()}

closedchan

可重用的关闭通道,该channel通道默认已关闭

// closedchan is a reusable closed channel.var closedchan = make(chan struct{})func init() {close(closedchan) // 调用close 方法关闭}

6、timerCtx 源码分析

cancelCtx源码已经分析完毕,那timerCtx理解起来就很容易。

关注点timerCtx是如何取消上下文的,以及取消上下文的方式

6.1、对象创建 WithDeadline和WithTimeout

WithTimeout 底层调用是WithDeadline 方法 ,截止时间是 now+timeout;

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {return WithDeadline(parent, time.Now().Add(timeout))}

WithDeadline 整体逻辑并不复杂,从源码中可分析出timerCtx取消上下文 采用两种方式 自动手动;其中自动方式采用定时器去处理,到达触发时刻,自动调用cancel方法。

deadline: 截止时间

timer *time.Timer : 定时器

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {if parent == nil {panic("cannot create context from nil parent")}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,}propagateCancel(parent, c)dur := time.Until(d)if dur <= 0 {c.cancel(true, DeadlineExceeded) // deadline has already passedreturn 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) }}
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to// implement Done and Err. It implements cancel by stopping its timer then// delegating to cancelCtx.cancel.type timerCtx struct {cancelCtxtimer *time.Timer // Under cancelCtx.mu.deadline time.Time}

6.2 timerCtx的cancel

func (c *timerCtx) cancel(removeFromParent bool, err error) {c.cancelCtx.cancel(false, err)if removeFromParent {// Remove this timerCtx from its parent cancelCtx's children.removeChild(c.cancelCtx.Context, c)}c.mu.Lock()if c.timer != nil {c.timer.Stop()c.timer = nil}c.mu.Unlock()}

7、valueCtx 源码分析

7.1、对象创建WithValue

valueCtx 结构体中有keyval两个字段,WithValue 方法也是将数据存放在该字段上

func WithValue(parent Context, key, val any) Context {if parent == nil {panic("cannot create context from nil parent")}if key == nil {panic("nil key")}if !reflectlite.TypeOf(key).Comparable() {panic("key is not comparable")}return &valueCtx{parent, key, val}}// A valueCtx carries a key-value pair. It implements Value for that key and// delegates all other calls to the embedded Context.type valueCtx struct {Contextkey, val any}

7.2、获取value值

func (c *valueCtx) Value(key any) any {if c.key == key { // 判断当前valuectx对象中的key是否匹配return c.val}return value(c.Context, key)}// value() 向根部方向遍历,直到找到与key对应的值func value(c Context, key any) any {for {switch ctx := c.(type) {case *valueCtx:if key == ctx.key {return ctx.val}c = ctx.Contextcase *cancelCtx:if key == &amp;cancelCtxKey { // 获取cancelCtx对象return c}c = ctx.Contextcase *timerCtx:if key == &amp;cancelCtxKey {return &amp;ctx.cancelCtx}c = ctx.Contextcase *emptyCtx:return nildefault:return c.Value(key)}}}

以上就是关于“Go中的Context怎么使用”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注编程网行业资讯频道。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯