context库

context最早的背景说明还是来源于官方的 博客,说明如下:

在Go服务器中,每个传入请求都在其自己的goroutine中进行处理。 请求处理程序通常会启动其他goroutine来访问后端,例如数据库和RPC服务。 处理请求的goroutine集合通常需要访问特定于请求的值,例如最终用户的身份,授权令牌和请求的期限。 当一个请求被取消或超时时,处理该请求的所有goroutine应该迅速退出,以便系统可以回收他们正在使用的任何资源。

再次背景之下,谷歌就开发了context的库,可以轻松地将跨API边界的请求范围的值,取消信号和截止日期传递给处理请求的所有goroutines。

context库的出现背景是为了解决一个goroutine退出,方便快速的让其他goroutine退出,从而提供资源的利用率。

源码

包上下文定义了上下文类型,其中包含截止日期,跨越API边界的取消信号和其他请求范围的值和进程之间。传入服务器的请求应创建一个上下文,然后传出对服务器的调用应接受上下文,功能链。它们之间的调用必须传播Context,可以选择替换它与使用WithCancel,WithDeadline,WithTimeout或WithValue。取消上下文后,所有
从中派生的上下文也会被取消。WithCancel,WithDeadline和WithTimeout函数采用上下文(父级)并返回派生的上下文(子级)和一个CancelFunc。调用CancelFunc取消子项及其子项子代,删除父代对子代的引用,然后停止任何关联的计时器。未能调用CancelFunc会泄漏子代及其子代,直到取消父代或定时器触发。审核工具检查所有对象上都使用了CancelFuncs控制流路径。使用上下文的程序应遵循以下规则来保留接口跨软件包保持一致,并启用静态分析工具来检查上下文传播:不要将Context存储在struct类型中;相反,传递一个上下文显式地提供给需要它的每个函数。上下文应该是第一个参数,通常命名为ctx:

 func DoSomething(ctx context.Context,arg Arg)error {...使用ctx ...
}

即使函数允许,也不要传递nil Context。传递上下文如果不确定使用哪个上下文,也可以传递context.TODO。仅将上下文值用于传递过程和请求的请求范围的数据API,而不是用于将可选参数传递给函数的API。相同的上下文可以传递给在不同goroutine中运行的函数;上下文可以安全地被多个goroutine同时使用。

以上就是源码头部注释的说明,从说明也可看出整个包的具体使用思路与功能。

WithCancel使用示例
package mainimport ("context""fmt""time"
)func main() {ctx, cancel := context.WithCancel(context.Background())// 开启一个协程运行go func() {for {select {case <- ctx.Done():fmt.Println("context done")return}}}()time.Sleep(2*time.Second)// 主协程取消cancel()fmt.Println("main cancel")// 为了让ctx.Done的协程能够打印出来context donetime.Sleep(2*time.Second)
}
context done
main cancel

输出如下,从该示例中就可以看出ctx传入到另一个协程中,cancel在不同的协程中来取消,从而完成了一个协程退出的通知机制。看了这个功能之后,大家平常使用的方式可能是如下代码的方式;

package mainimport ("fmt""time"
)func main() {ch := make(chan struct{})go func(){for {select {case <- ch:fmt.Println("done")return}}}()time.Sleep(2*time.Second)fmt.Println("mian done")ch <- struct{}{}time.Sleep(2*time.Second)}
mian done
done

运行结果如下,得出的效果跟用WithCancel效果相同,那我们就来查看一下WithCancel的源码是如何实现的。

//位于context.go文件
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {c := newCancelCtx(parent)propagateCancel(parent, &c)return &c, func() { c.cancel(true, Canceled) }
}// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {return cancelCtx{Context: parent}
}// propagateCancel arranges for child to be canceled when parent is.
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():}}()}
}

从代码中可以看见WithCancel就是通过parent生成一个cancelCtx实例,然后将该实例加入到parent的孩子队列中,这样就建立了从parent到child的关系。

然后再函数返回的时候不仅返回了cancelCtx实例,还返回了一个cancel函数,该函数就是调用cancelCtx实例的cancel方法。我们继续查看cancel方法的流程。

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)    // 关闭c.done的通道  这样就通知取消}for child := range c.children {// NOTE: acquiring the child's lock while holding parent's lock.child.cancel(false, err)    // 调用所有子child的关闭函数关闭}c.children = nilc.mu.Unlock()if removeFromParent {removeChild(c.Context, c)  // 如果需要从父那边移除则移除该c}
}

从用了Lock的函数可知,ctx是线程安全的操作,而且context使用了树形的通知结构,所有添加的子节点都会执行取消的函数,并且取消之后会从父的ctx中移除该ctx。从这个函数也可以看一下整个cancelCtx的定义可知,所有的机制都是基于上述的思路,通过chan来通知不同协程中的监听事件。大致思路明白之后,我们再看看其他的用法。

WithDeadline使用示例
package mainimport ("context""fmt""time"
)func main() {d := time.Now().Add(50 * time.Millisecond)ctx, cancel := context.WithDeadline(context.Background(), d)defer cancel()select {case <-time.After(1 * time.Second):fmt.Println("overslept")case <-ctx.Done():fmt.Println(ctx.Err())}
}
context deadline exceeded

从输出可以看出,在五十毫秒之后关闭了ctx.Done()返回的chan,假如我们将五十毫秒调整到大于1秒,此时就会输出overslept。该功能可以让ctx在协程的传递过程中,设置超时机制。我们看一下具体的源码。

// 位于context.go
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {if cur, ok := parent.Deadline(); ok && cur.Before(d) {  // 检查是否大于parent的过期时间// The current deadline is already sooner than the new one.return WithCancel(parent)                             // 大于当前时间则使用parent的过期时间}c := &timerCtx{cancelCtx: newCancelCtx(parent),deadline:  d,}                              // 生成一个timerCtx实例propagateCancel(parent, c)     // 加入到parent中dur := time.Until(d)       if dur <= 0 {c.cancel(true, DeadlineExceeded) // deadline has already passedreturn c, func() { c.cancel(false, Canceled) }   // 假如当前时间已经过了先执行取消,然后返回cancel函数提供程序手动调用}c.mu.Lock()defer c.mu.Unlock()if c.err == nil {c.timer = time.AfterFunc(dur, func() {                   // 添加一个过期时间函数,过了dur时间后调用c.cancel函数c.cancel(true, DeadlineExceeded)})}return c, func() { c.cancel(true, Canceled) }     // 正常返回
}

从WithDeadline的实现过程来看,主要就是添加了time.AfterFunc函数,来设置一个过期回调函数来关闭通道。

总结

context包是一个线程安全,易于使用的库,从而提供了一种简便的方式来实现跨协程的通行机制,很多其他的包也都基于context包来实现对应的资源回收、消息通知等工作,现在应用比较广泛的就是我们大家查用的web框架中,大量的使用了context包来进行业务的处理,context的源码不多但是设计精巧很值得,很值得学习与借鉴。由于本人才疏学浅,如有错误请批评指正。

golang库context学习相关推荐

  1. Go学习笔记—标准库Context

    标准库Context ​ 由于goroutine没有父子关系,多个goroutine都是被平行的调度,所以在拉起多个goroutine后,程序的执行模型并没有维护树状结构的goroutine树,所以无 ...

  2. 一文搞懂Go标准库context包

    "Gopher部落"星球双11现金优惠,点击链接领劵 https://t.zsxq.com/078E1QTjM  立减88元. 自从context包在Go 1.7版本[1]加入Go ...

  3. go context学习

    context学习 context的struct设计 type Context interface {Deadline() (deadline time.Time, ok bool)Done() &l ...

  4. Golang 库: 为什么 Golang slog 库不支持 `slog.Fatal` API

    Golang 库: 为什么 Golang slog 库不支持 slog.Fatal API 原文链接: https://tangx.in/posts/2023/01/06/why-dont-golan ...

  5. python内置库之学习configparser库(一)

    python内置库之学习configparser库(一) 1.引言 ini文件简介 [节] 键=值 注:节不能重复出现 2.自己封装了一个增删改查的类,可以参考一下 import configpars ...

  6. go标准库的学习-crypto/sha1

    参考:https://studygolang.com/pkgdoc 导入方式: import "crypto/sha1" sha1包实现了SHA1哈希算法,参见RFC 3174. ...

  7. 日志库EasyLogging++学习系列(10)—— 日志文件滚动

    在很多应用场合,我们是需要实现日志文件滚动的,特别是在一些长期运行的服务器程序中,如果把所有的日志都记录在一个文件之中,势必会造成日志文件越来越大.当日志内容很多的时候,万一哪天突然需要查询某个日志信 ...

  8. 日志库EasyLogging++学习系列(7)—— 记录方式详解

    在前面所列文章的演示代码中,其实已经展示了一部分记录日志的方式.为了使用方便,在 Easylogging++ 中,通过使用宏的集合来完成日志记录. 普通日志记录 对于普通的日志记录,我们可以选择以下两 ...

  9. 日志库EasyLogging++学习系列(6)—— 日志记录器

    所有的日志都是由日志记录器完成的,日志记录器使用唯一的 ID(大小写敏感)来标识.在 Easylogging++ 中默认了三个现有的日志记录器: 默认日志记录器,其 ID 为:default 性能日志 ...

最新文章

  1. python利器app-有了这个神器,轻松用 Python 写 APP !
  2. laravel 邮件SwiftMailer
  3. **52.常用的存储保护方法有哪些?
  4. sharepoint部件webparth关闭找回的方法
  5. 1092 最好吃的月饼 (20分)_24行代码AC
  6. python学习笔记(2
  7. 自动驾驶7-2 最终项目概述 Final Project Overview
  8. EPM连接显示服务器不可用,EPM问题汇总之-SmartView没法链接Essbase
  9. java中常见的设计模式_在Java中10种常见设计模式详细介绍
  10. java成语填充,java - 什么是“执行”这个成语?
  11. WindRiver WorkBench创建、编译vxWorks APP工程
  12. 深度学习之CNN反向传播
  13. 大数运算:Barrett And Montgomery
  14. 演出经纪人考试大纲、演出经纪人考试资料是什么?
  15. OffscreenCanvas-离屏canvas使用说明
  16. 我的大三一年职业规划,预期毕业目标
  17. vue统一或单独控制接口请求时间
  18. 数据化运营chapter4_code--分析模型和方法
  19. 关于小白如何查看自己的文章是否被EI检索
  20. 浏览器底层,内存分配,运行机制

热门文章

  1. 有哪些新手程序员不知道的小技巧?
  2. 百度飞桨全新升级:重磅推出PaddleHelix平台、开源框架V2.0RC,硬件生态路线图全公开...
  3. 吴恩达:AI未来将呈现四大发展趋势
  4. 程序员假冒AI,印度公司竟骗取2亿元投资
  5. 性能提升3倍的树莓派4,被爆设计缺陷!
  6. “智能+”时代,看见别人看不见的才是赢家
  7. 叫你一声“孙悟空”,敢答应么?
  8. 人工智能“花”落何处
  9. JDK1.8 中的双冒号::是什么语法?
  10. 五分钟体验分布式事务框架Seata