简要说明

gorilla/context 用于在请求期间保存状态,是一个很好的解决多goroutine下通知传递和元数据的Go标准库。由于Go中的goroutine之间没有父子关系,因此也不存在子进程退出后的通知机制。多个goroutine协调工作涉及通信,同步,通知,退出四个方面,但由于goroutine之间地位平等,因此当遇到复杂的并发结构时处理退出机制则会显得力不从心。因此Go1.7版本开始提供了context标准库来解决这个问题。他提供两个功能:退出通知和元数据传递。

源码分析

context接口如下,其中Deadline会返回一个超时时间,Goroutine获得了超时时间后,例如可以对某些 io 操作设定超时时间;Done 方法返回一个信道,当 Context 被撤销或过期时,该信道是关闭的,即它是一个表示 Context 是否关闭的信号;当 Done 信道关闭后,Err方法表明 Context 被撤销的原因;Value 可以让 Goroutine 共享一些数据,当然获得数据是协程安全的。但是使用这些数据的时候,需要注意同步,比如返回了一个 map,而这个 map 的读写则要加锁:

type Context interface {Deadline() (deadline time.Time, ok bool)Done() <-chan struct{}Err() errorValue(key interface{}) interface{}
}
context中的数据结构:
  1. cancelCtx的数据结构如下,其中Context接口保存父类的context,children map[canceler]struct{}保存的是所有直属与这个context的子类context。done chan struct{}用于发送退出信号:
type cancelCtx struct {Contextmu       sync.Mutex            // protects following fieldsdone     chan struct{}         // created lazily, closed by first cancel callchildren map[canceler]struct{} // set to nil by the first cancel callerr      error                 // set to non-nil by the first cancel call
}
  1. timerCtx的数据结构如下,timerCtx继承于cancelCtx,并为定时退出功能新增自己的数据结构。timerCtx可以用timerCtx.cancelCtx.Context的方法查看parent context:
type timerCtx struct {cancelCtxtimer *time.Timer // Under cancelCtx.mu.deadline time.Time
}
  1. valueCtx的数据结构如下,它没有继承自cancelCtx struct,而是直接继承自Context接口:
type valueCtx struct {Contextkey, val interface{}
}
调用这些数据结构的API如下:
  1. WithCancel(parent Context) (Context, CancelFunc):创建cancelCtx实例。它是将父节点复制到子节点,并且还返回一个额外的 CancelFunc 函数类型变量。其中propagateCancel函数的作用是将自己注册至parent context。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {c := newCancelCtx(parent)propagateCancel(parent, &c)return &c, func() { c.cancel(true, Canceled) }
}
func newCancelCtx(parent Context) cancelCtx {return cancelCtx{Context: parent}
}
  1. WithDeadline(parent Context, d time.Time) (Context, CancelFunc) :创建timerCtx实例,并定义了定时退出机制相关内容。它返回 Context 的类型是 parent 的副本,但其过期时间由 deadline 和 parent 的过期时间同时决定。当 parent 的过期时间早于传入的 deadline 时间时,返回的过期时间应与 parent 相同。父节点过期时,其所有的子孙节点必须同时关闭;反之,返回的父节点的过期时间则为 deadline:
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,}propagateCancel(parent, c)dur := time.Until(d)if dur <= 0 {c.cancel(true, DeadlineExceeded) // deadline has already passedreturn c, func() { c.cancel(true, 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) }
}
func newCancelCtx(parent Context) cancelCtx {return cancelCtx{Context: parent}
}
  1. WithValue(parent Context, key, val interface{}) Context:创建valueCtx实例。它返回 parent 的一个函数副本,调用该副本的 Value(key) 方法将得到 val。这样,我们不光将根节点的原有的值保留了,还在孙节点中加入了新的值,注意若存在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}
}
一些辅助函数
  1. propagateCancel()负责注册信息,代码如下,该函数接收parent context 和 child canceler方法,若parent为emptyCtx,则不注册;否则通过funcparentCancelCtx寻找最近的一个*cancelCtx;若该cancelCtx已经结束,则调用child的cancel方法,否则向该cancelCtx注册child:
func propagateCancel(parent Context, child canceler) {if parent.Done() == nil {return // parent is never canceled}if p, ok := parentCancelCtx(parent); ok {p.mu.Lock()if p.err != nil {// parent has already been canceledchild.cancel(false, p.err)} else {if p.children == nil {p.children = make(map[canceler]struct{})}p.children[child] = struct{}{}}p.mu.Unlock()} else {go func() {select {case <-parent.Done():child.cancel(false, parent.Err())case <-child.Done():}}()}
}
  1. parentCancelCtx()函数代码如下,它从parentCtx中向上迭代寻找第一个cancelCtx并返回。从函数逻辑中可以看到,只有当parent.(type)为valueCtx的时候,parent才会向上迭代而不是立即返回,否则该函数都是直接返回或返回经过包装的*cancelCtx:
func parentCancelCtx(parent Context) (*cancelCtx, bool) {for {switch c := parent.(type) {case *cancelCtx:return c, truecase *timerCtx:return &c.cancelCtx, truecase *valueCtx:parent = c.Contextdefault:return nil, false}}
}
  1. func (c *cancelCtx) cancel函数中,若外部err为空,则代表这是一个非法的cancel操作,抛出panic;若cancelCtx内部err不为空,说明该Ctx已经执行过cancel操作,直接返回;关闭done channel,关联该Ctx的goroutine收到退出通知;遍历children,若有的话,执行child.cancel操作;调用removeChild将自己从parent context中移除:
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 = errif c.done == nil {c.done = closedchan} else {close(c.done)}for child := range c.children {child.cancel(false, err)}c.children = nilc.mu.Unlock()if removeFromParent {removeChild(c.Context, c)}
}

从上述代码可见Context如何保存父类和子类上下文,以及cancel方法实现了,对于存在父子关系的ctx,一旦cancel父ctx,所有子ctx一起cancel的退出功能。

Gorilla/context库分析相关推荐

  1. Go语言经典库使用分析(二)| Gorilla Context

    Go语言经典库使用分析,未完待续,欢迎扫码关注公众号flysnow_org或者网站http://www.flysnow.org/,第一时间看后续系列.觉得有帮助的话,顺手分享到朋友圈吧,感谢支持. 在 ...

  2. Gorilla源码分析之gorilla/context源码分析

    公众号文章链接:https://mp.weixin.qq.com/s/KogOt-hd6KvXljwYwK7aQg csdn博客链接:http://blog.csdn.net/screscent/ar ...

  3. Gorilla源码分析之gorilla/mux源码分析

    本文公众号文章链接:https://mp.weixin.qq.com/s/LLcPDPtpjNeXAA_ffL3YCg 本文csdn博客链接:http://blog.csdn.net/screscen ...

  4. Kotlin协程核心库分析-5 Job异常处理器注意点

    本章我们简单探讨一下异常处理: 我们知道协程传入CoroutineExceptionHandler对象即可捕获异常,那么对于子协程是否适用呢? fun main() {val eChild = Cor ...

  5. Go实战--Gorilla web toolkit使用之gorilla/context

    感慨: 小说<人间失格> 保温杯,枸杞 中兴程序员跳楼 朴树演到"情千缕,酒一杯,声声离笛催"时的哽咽 <芳华>,芳华已逝,面目全非 -- 哎,生活不易. ...

  6. python 颜色_如何使用python中matplotlib库分析图像颜色

    用代码分析图像可能很困难.你如何使代码"理解"图像的上下文? 通常,使用AI分析图像的第一步 是找到主要颜色.在如何使用python中matplotlib库分析图像颜色中,我们将使 ...

  7. Seurat的单细胞免疫组库分析来了!

    使用Seurat进行单细胞VDJ免疫分析 NGS系列文章包括NGS基础.转录组分析 (Nature重磅综述|关于RNA-seq你想知道的全在这).ChIP-seq分析 (ChIP-seq基本分析流程) ...

  8. python数据挖掘课程】十七.社交网络Networkx库分析人物关系(初识篇)

    #2018-03-30 09:21:39 March Friday the 13 week, the 089 day SZ SSMR python数据挖掘课程]十七.社交网络Networkx库分析人物 ...

  9. stm32 HAL库分析之CAN

    stm32 HAL库分析之CAN 阻塞发送 HAL_StatusTypeDef HAL_CAN_Transmit(CAN_HandleTypeDef* hcan, uint32_t Timeout) ...

最新文章

  1. 知乎社区核心业务 Golang 化实践
  2. Linux Shell的输入彩色字体
  3. poj 3579 Median 中间值(二分搜索)
  4. 已经无法合并还报请合并git_GIT 分支管理:创建与合并分支、解决合并冲突
  5. module.js:549 throw err;
  6. 开源自己用python封装的一个Windows GUI(UI Automation)自动化工具,支持MFC,Windows Forms,WPF,Metro,Qt...
  7. 【POJ - 1696】Space Ant (凸包,最小极角,排序)
  8. python维度变换_Python NumPy用法
  9. 1003 C语言输入以某个特殊输入为结束标志
  10. JS 正则(RegExp)
  11. python 装饰器(复杂一点的)
  12. mysql 命令之工作小结
  13. r4烧录卡内核安装_玩家必看!教你彻底玩转R4烧录卡(下)
  14. 4199 公约数(求解约数 + 最大公约数 + 二分)
  15. 进击的蚂蚁金融云与场景焦虑的银行
  16. Python八个自动化办公的技巧
  17. python名片系统代码练习并存储到数据库中
  18. 探究dosbox打印字符时的bug问题
  19. PUT、POST的区别:
  20. Axure this is most likely not a valida .rp file

热门文章

  1. UE5黑客帝国3d print假室内的一些使用记录
  2. revit 2021 r2(3D建筑信息模型构建软件)pjb 附安装教程
  3. Requests模块设置Header的User-Agent
  4. 蓝桥杯 算法训练 无聊的逗
  5. 全自动生成、设置课表壁纸【完结】
  6. 蓝桥杯python组——猜生日
  7. python安装第三方包_python 怎么安装第三方包
  8. 子弹散射——Unity随手记(2021.2.4)
  9. 题解报告:hdu 2188 悼念512汶川大地震遇难同胞——选拔志愿者(巴什博弈)
  10. 论文笔记:Universal Adversarial Triggers for Attacking and Analyzing NLP