Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Context源码,再度重相逢

Context源码,再度重相逢

作者头像
薯条的编程修养
发布于 2022-08-10 11:41:36
发布于 2022-08-10 11:41:36
24700
代码可运行
举报
运行总次数:0
代码可运行

各位读者朋友们大家好,我是随波逐流的薯条。深秋了,前几天气温骤降,北京的人和狗都不愿意出门,趴在窝里冻的打寒颤。我的书房里没装空调,暖气要十一月中旬才来,每次想学习都得下很大的决心,所以这篇文章发出来时比预期又晚了几天~

最近我的心也是冰冰的,我目前做在线数据开发,如果大家干过这活肯定知道,数据开发最重要的是数据口径,开发前一定得对清楚... 本人作为在这上面踩了很多坑的人这几天接需求时又掉进去了,一个需求在已经上线的情况下,不同来源的数据做diff总是对不上,一查就是口径不对,来来回回改了根据口径改了一遍逻辑,搞得我tm的真想和提供口径的人打一架,md。

有点扯远了,言归正传,这篇文章接着上次的 Context这三个应用场景,你知吗 继续看看context源码,读者可能觉得【Context源码,再度重相逢】这个标题比较奇怪。起这个题目是因为在下 读context源码时找了一些资料,最好的中文资料应是【码农桃花源】qcrao在19年写过的一篇关于context解析的文章,所以我在犹豫要不要写我的这篇,说实话代码都看完了不写出来吹吹牛逼总觉得有点亏。好在rao老板分析context源码基于的Go版本是1.9.2,如今Go已经1.17了,context的源码也有很多更新,于是不才就来一篇基于1.17.2的context源码分析,不多说了,发车!


  • 源码分析
    • ctx存储键值对
    • ctx的取消机制
  • 源码赏析
    • if >= 2 用switch替换
    • atomic.Value 替换chan struct 减少锁使用
    • 加锁前,先获取值避免加锁
    • String逻辑赏析
    • 一个Bug
  • 总结

源码分析

context的核心作用是存储键值对和取消机制。存储键值对比较简单,取消机制比较复杂,先来看一下Context抽象出来的接口:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type Context interface {
  // 如果是timerCtx或者自定义的ctx实现了此方法,返回截止时间和true,否则返回false
 Deadline() (deadline time.Time, ok bool)
  // 这里监听取消信号
  Done() <-chan struct{}
  // ctx取消时,返回对应错误,有context canceled和context deadline exceeded
  Err() error 
  // 返回key的val
 Value(key interface{}) interface{}
}

ctx存储键值对

键值对ctx比较简单,先来看一下它的逻辑:要新建一个存储键值对的ctx,需要调用WithValue,它返回一个valueCtx地址对象。valueCtx结构体内部很简单,有个Context接口和k-v对:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type valueCtx struct {
 Context
 key, val interface{}
}

valueCtx实现了Value方法,逻辑也很简单:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (c *valueCtx) Value(key interface{}) interface{} {
  // key相同则返回key
  if c.key == key {
  return c.val
 }
  // 否则从父节点中调用Value方法继续寻找key
 return c.Context.Value(key)
}

写一段代码看一下从valueCtx中查找某个key的过程:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func main() {
 ctx := context.Background()

 ctx1 := context.WithValue(ctx, "name", "uutc")
 ctx2 := context.WithValue(ctx1, "age", "18")
 ctx3 := context.WithValue(ctx2, "traceID", "89asd7yu9asghd")

 fmt.Println(ctx3.Value("name"))
}

valueCtx是个链表模型,当我们从ctx3中查找name这个key, 最终要走到ctx1中才能返回对应的value,如图所示:

虽然链表的查找效率是O(n)的,但貌似一个请求里面也不会有1000个ctx,个人认为ctx链表的查找效率可以接受,读者有兴趣可以给go团队提个pr,把链表改成红黑树试试,嘿嘿~

ctx的取消机制

context的取消机制我个人认为可以分成两种:第一种是普通取消,需要取消ctx时调用cancel函数。第二个是根据时间取消,用户可以定义一个过期time或一个deadline,到这个时间时自动取消。

普通取消

现在假装没看源码,联想一下如果我们自己实现。该如何写取消。 建立ctx时, 是在parent的基础上copy一份,然后添加自己的属性, 不同协程可能持有不同的ctx,若想在请求层面做协程取消,就需要广播机制,比如在下图中:

img

若我们要取消ctx2,应分为向上取消向下取消两部分,向下取消要把当前节点的子节点都干掉,在这里需要tx4、ctx5都取消掉;而向上取消需要把他在父节点中删除,在这里需要把自己(ctx2)从父节点ctx的子节点列表中删除;

取消这个动作本身并没有神奇的地方。ctx创建一个channel,然后协程通过select去监听这个channel,没数据时处于阻塞状态,当调用取消函数,函数内部执行close(chan)操作, select监听到关闭信号执行return,达到取消协程的目的,写个demo:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func main() {
 done := make(chan struct{})

 go func() {
  close(done)
 }()

 select {
 case <-done:
  println("exit!")
  return
 }
}

下面来看go源码是怎么实现的取消,首先抽象出了一个canceler接口,这个接口里最重要的就是cancel方法,调用这个方法可以发送取消信号,有两个结构体实现了这个接口,分别是 *cancelCtx(普通取消) 和 *timerCtx(时间取消)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type canceler interface {
 cancel(removeFromParent bool, err error)
 Done() <-chan struct{}
}

cancelCtx对应前文说的普通取消机制,它是context取消机制的基石,也是源码中比较难理解的地方,先来看一下它的结构体:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type cancelCtx struct {
 Context

 mu       sync.Mutex            // context号称并发安全的基石
 done     atomic.Value          // 用于接收ctx的取消信号,这个数据的类型做过优化,之前是 chan struct 类型
 children map[canceler]struct{} // 储存此节点的实现取消接口的子节点,在根节点取消时,遍历它给子节点发送取消信息
 err      error                 // 调用取消函数时会赋值这个变量
}

若我们要生成一个可取消的ctx,需要调用WithCancel函数,这个函数的内部逻辑是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
 if parent == nil {
  panic("cannot create context from nil parent")
 }
 c := newCancelCtx(parent)    // 基于父节点,new一个CancelCtx对象
 propagateCancel(parent, &c)  // 挂载c到parent上
 return &c, func() { c.cancel(true, Canceled) } // 返回子ctx,和返回函数
}

这里逻辑比较重的地方是propagateCancel函数和cancel方法,propagateCancel函数主要工作是把子节点挂载父节点上,下面来看看它的源码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func propagateCancel(parent Context, child canceler) {
 done := parent.Done()
  // 判断父节点的done是否为nil,若为nil则为不可取消的ctx, 直接返回
 if done == nil {
  return
 }
  // 看能否从done里面读到数据,若能说明父节点已取消,取消子节点,返回即可,不能的话继续流转到后续逻辑
 select {
 case <-done:
  child.cancel(false, parent.Err())
  return
 default:
 }

  // 调用parentCancelCtx函数,看是否能找到ctx上层最接近的可取消的父节点
 if p, ok := parentCancelCtx(parent); ok {
    //这里是可以找到的情况
  p.mu.Lock()
  // 父节点有err,说明已经取消,直接取消子节点
    if p.err != nil {
   child.cancel(false, p.err)
  } else {
      // 把本节点挂载到父节点的children map中
   if p.children == nil {
    p.children = make(map[canceler]struct{})
   }
   p.children[child] = struct{}{}
  }
  p.mu.Unlock()
 } else {
    // 若没有可取消的父节点挂载
  atomic.AddInt32(&goroutines, +1)
  // 新起一个协程
    go func() {
   select {
      // 监听到父节点取消时,取消子节点
   case <-parent.Done():
    child.cancel(false, parent.Err())
      // 监听到子节点取消时,什么都不做,退出协程
   case <-child.Done():
   }
  }()
 }
}

我看这段源码时产生了两个问题:

  1. 函数内部的 parentCancelCtx 函数中有个else分支,什么条件下会走到这里
  2. 调用cancel方法需要传递bool值,何时传true,何时传false

经过一番研究,大概解决了这俩问题,下面依次做解答。

什么条件下会走到parentCancelCtx 函数的else分支

首先看下parentCancelCtx 函数的逻辑。parentCancelCtx函数用来查找ctx最近的一个可取消的父节点,这个函数也经过了优化,原代码是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
 for {
  switch c := parent.(type) {
  case *cancelCtx:
   return c, true
  case *timerCtx:
   return &c.cancelCtx, true
  case *valueCtx:
   parent = c.Context
  default:
   return nil, false
  }
 }
}

这段代码比较简单,起了一个for循环,遇到*cancelCtx*timerCtx类型就返回,遇到*valueCtx则继续向上查找parent,直到找到了节点或者找不到为止。

最新版本的代码放弃粗暴的使用for{}循环寻找父节点,而是用parent.Value方法查到父节点是否能找到特定的key,由于Value是递归的,所以这里表面上看不出来循环的足迹:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
 done := parent.Done()
 if done == closedchan || done == nil {
  return nil, false
 }
 // Value是递归向上查找,直到找到有*cancelCtxKey 的ctx为止
 p, 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
}

知道了这个前提,我们继续研究什么条件下会走到parentCancelCtx 函数的else分支。我自己实现了一个Context,代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type ContextCancel struct {
 context.Context
}

func (*ContextCancel) Done() <-chan struct{} {
 ch := make(chan struct{}, 100)
 return ch
}

当调用这段代码时,即可走到else分支,写个demo:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func main() {
 ctx := context.Background()
 ctx1, _ := context.WithCancel(ctx)
 ctx2 := context.WithValue(ctx1, "hello", "world")
 ctx3 := ContextCancel{ctx2}
 ctx4, _ := context.WithCancel(&ctx3)  // 这里可以走到else分支

 println(ctx4)
}

与源码中CancelCtx不同的是,我这里的Done方法只是简单返回,并没有把done的值存到Context中去。所以在执行parentCancelCtx时,这里会判断失败,返回false:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
pdone, _ := p.done.Load().(chan struct{})
if pdone != done {
 return nil, false
}

通过parent.Value(&cancelCtxKey).(*cancelCtx)虽然找到了cancelCtx,但是在load Done方法值的时候却铩羽而归,parentCancelCtx 这里判断失败,最终返回nil和false,最终走到else分支。所以这个else分支主要是预防用户自己实现了一个定制的Ctx中,随意提供了一个Done chan的情况的,由于找不到可取消的父节点的,只好新起一个协程做监听。

调用cancel方法需要传递bool值,何时传true,何时传false

要明白这个问题,先来看一下*cancelCtx类型的cancel方法实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
 // 取消时必须传入err,不然panic
  if err == nil {
  panic("context: internal error: missing cancel error")
 }
 c.mu.Lock()
  // 已经出错了,说明已取消,直接返回
 if c.err != nil {
  c.mu.Unlock()
  return
 }
  // 用户传进来的err赋给c.err
 c.err = err
 d, _ := c.done.Load().(chan struct{})
 if d == nil {
    // 这里其实和关闭chan差不多,因为后续会用closedchan作判断
  c.done.Store(closedchan)
 } else {
    // 关闭chan
  close(d)
 }
  // 这里是向下取消,依次取消此节点所有的子节点
 for child := range c.children {
  child.cancel(false, err)
 }
  // 清空子节点
 c.children = nil
 c.mu.Unlock()
  // 这里是向上取消,取消此节点和父节点的联系
 if removeFromParent {
  removeChild(c.Context, c)
 }
}

removeChild函数的逻辑比较简单,核心就是调用delete方法,在父节点的子节点中清空自己。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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()
}

看完这俩函数的逻辑后,这个问题也可以回答。当父节点调用cancel函数时传递true, 其他情况传递false。

true用来向上删除,核心逻辑是调用removeChild函数里面的的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if p.children != nil {
  delete(p.children, child) // 这里只是删除一个
}

而false就是用来非向上删除,只需要执行完cancel方法这段代码即可:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
for child := range c.children {
  child.cancel(false, err) // 这里把子节点都干掉
}

看到这里,ctx的普通取消机制基本差不多了,下面来看一下基于时间的取消机制。

时间取消

时间取消ctx可传入两种时间,第一种是传入超时时间戳;第二种是传入ctx持续时间,比如2s之后ctx取消,持续时间在实现上是在time.Now的基础上加了个timeout凑个超时时间戳,本质上都是调用的WithDeadline函数。

WithDeadline 函数内部new了一个timerCtx,先来看一下这个结构体的内容:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type timerCtx struct {
 cancelCtx
 timer *time.Timer  // 一个统一的计时器,后续通过 time.AfterFunc 使用
 deadline time.Time // 过期时间戳
}

可以看到 timerCtx 内嵌了cancelCtx,实际上在超时取消这件事上,timerCtx更多负责的是超时相关的逻辑,而取消主要调用的cancelCtx的cancel方法。先来看一下WithDeadline函数的逻辑,看如何返回一个时间Ctx:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
 // 若父节点为nil,panic
 if parent == nil {
  panic("cannot create context from nil parent")
 }
 // 如果parent有超时时间、且过期时间早于参数d,那parent取消时,child 一定需要取消,直接通过WithCancel走起
 if cur, ok := parent.Deadline(); ok && cur.Before(d) {
  // The current deadline is already sooner than the new one.
  return WithCancel(parent)
 }
 // 构造一个timerCtx, 主要传入一个过期时间
 c := &timerCtx{
  cancelCtx: newCancelCtx(parent),
  deadline:  d,
 }
 // 把这个节点挂载到父节点上
 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)
  })
 }
 // 返回一个ctx和一个取消函数
 return c, func() { c.cancel(true, Canceled) }
}

看完源码可以知道,除了基于时间的取消,当调用CancelFunc时,也能取消超时ctx。再来看一下*timerCtx的cancel方法的源码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (c *timerCtx) cancel(removeFromParent bool, err error) {
 // 调用cancel的cancel取消掉它下游的ctx
 c.cancelCtx.cancel(false, err)
 // 取消掉它上游的ctx的连接
 if removeFromParent {
  removeChild(c.cancelCtx.Context, c)
 }
 // 把timer停掉
 c.mu.Lock()
 if c.timer != nil {
  c.timer.Stop()
  c.timer = nil
 }
 c.mu.Unlock()
}

至此,context源码分析的差不多了,关于这块还有个挺常见的问题,Context.TODO和Context.Backend的区别。从代码上看,他俩没啥区别,都只是emptyCtx的对象而已,emptyCtx源码很简单,这里不再赘述。

源码赏析

写这篇文章时,我在想看源码的好处什么。个人认为有两点,第一可以从源码角度看到一个概念的全部细节,第二个是可以学习大牛写代码的思路。实际上context的代码也有个迭代过程,下面列举一些阅读源码时学习到的点:

if >= 2 用switch替换

来自String() string方法。

atomic.Value 替换chan struct 减少锁使用

来自cancelCtx源码,用atomic.Value类型替换了chan struct{}。

加锁前,先获取值避免加锁

这点在go源码中随处可见,简单列举几处:

看源码时就感觉这个select有点突兀,一看果然为优化效率后加的~

String逻辑赏析

之前的源码只是粗暴的使用Sprintf函数,后来自己搞了个stringer接口,用反射去打印Context。

一个Bug

截止日期已经过了,cancel已经执行过了,没必要在返回取消函数中再从父ctx中取消自己了. 感觉removeFromParent有点没抽象好,这不,作者自己都掉坑里去了。

总结

个人感觉context代码挺值得一看的:struct里面嵌套interface,struct并不对外暴露,而是提供多个Withxxx方法新建对象;interface对外暴露,用户可以根据需要构建自己的Context;timerCtx struct里面嵌套CancelCtx struct以此来复用cancel的逻辑等等。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-10-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 薯条的编程修养 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
go context详解
在 Go 服务器中,每个传入的请求都在其自己的 goroutine 中处理。请求处理程序通常会启动额外的 goroutine 来访问数据库和 RPC 服务等后端。处理请求的一组 goroutine 通常需要访问特定于请求的值,例如最终用户的身份、授权令牌和请求的截止日期。当请求被取消或超时时,所有处理该请求的 goroutines 都应该快速退出,以便系统可以回收它们正在使用的任何资源。
Johns
2022/06/22
2K0
深入Go:Context
在理解了 package context 的使用后,我们很自然地想问其背后的设计哲学有什么?实际上,我们发现无论是在关于 Context 的批评/讨论也不少,那么 Context 的设计合不合理?带着这些疑虑,我们深入 context 的源码,尝试对这些问题作出解答。
wenxing
2021/12/14
8260
深入Go:Context
轻松上手!手把手带你掌握从Context到go设计理念
: 导语 | 本文推选自腾讯云开发者社区-【技思广益 · 腾讯技术人原创集】专栏。该专栏是腾讯云开发者社区为腾讯技术人与广泛开发者打造的分享交流窗口。栏目邀约腾讯技术人分享原创的技术积淀,与广泛开发者互启迪共成长。本文作者是腾讯后端开发工程师陈雪锋。 context包比较小,是阅读源码比较理想的一个入手,并且里面也涵盖了许多go设计理念可以学习。 go的Context作为go并发方式的一种,无论是在源码net/http中,开源框架例如gin中,还是内部框架trpc-go中都是一个比较重要的存在,而整个 c
腾讯云开发者
2022/09/27
3960
轻松上手!手把手带你掌握从Context到go设计理念
Go组件:context学习笔记!
导语 | 最近学习go有一段时间了,在网上一直看到别人推荐,学go可以学习里面的context源码,短小精悍。看了下确实有所收获,本文是基于我最近对context源码学习的一些心得积累,望大家不吝赐教。 一、为什么使用Context (一)go的扛把子 要论go最津津乐道的功能莫过于go强大而简洁的并发能力。 func main(){ go func(){ fmt.Println("Hello World") }()} 通过简单的go func(){},go可以快速生成新的协程并运行。
腾讯云开发者
2022/08/26
3910
Go组件:context学习笔记!
go context原理
在 go 语言开发中, context 用于提供上下文的联系, 在不同协程调用间建立取消和超时机制,也可以用于传递相关值。
leobhao
2024/04/01
1430
深入解析Golang之context
context翻译成中文就是上下文,在软件开发环境中,是指接口之间或函数调用之间,除了传递业务参数之外的额外信息,像在微服务环境中,传递追踪信息traceID, 请求接收和返回时间,以及登录操作用户的身份等等。本文说的context是指golang标准库中的context包。Go标准库中的context包,提供了goroutine之间的传递信息的机制,信号同步,除此之外还有超时(timeout)和取消(cancel)机制。概括起来,Context可以控制子goroutine的运行,超时控制的方法调用,可以取消的方法调用。
数据小冰
2022/08/15
1.3K0
深入解析Golang之context
浅析golang中的context
go1.7才引入context,译作“上下文”,实际也叫goroutine 的上下文,包含 goroutine 的运行状态、环境、现场等信息、context 主要用来在 goroutine 之间传递上下文信息,包括:取消信号、超时时间、截止时间、k-v 等。与WaitGroup最大的不同点是context对于派生goroutine有更强的控制力,它可以控制多级的goroutine
素履coder
2022/02/17
1K0
浅析golang中的context
Go Context解析 A Brief Inquiry Into Go Context
Package context defines the Context type, which carries deadlines,
takeonme.
2021/11/26
9363
深度解密Go语言之context
Go 语言的 context 包短小精悍,非常适合新手学习。不论是它的源码还是实际使用,都值得投入时间去学习。
梦醒人间
2019/06/15
8440
Go Context 详解之终极无惑
Go 1.7 标准库引入 Context,中文名为上下文,是一个跨 API 和进程用来传递截止日期、取消信号和请求相关值的接口。
恋喵大鲤鱼
2022/05/09
5.1K0
Go Context 详解之终极无惑
一日一学_Go语言Context(设计及分析)
Go服务器的每个请求都有自己的goroutine,而有的请求为了提高性能,会经常启动额外的goroutine处理请求,当该请求被取消或超时,该请求上的所有goroutines应该退出,防止资源泄露。那
李海彬
2018/03/28
1.1K0
一日一学_Go语言Context(设计及分析)
Golang 笔记(二):Context 源码剖析
Context 是 Go 中一个比较独特而常用的概念,用好了往往能事半功倍。但如果不知其然而滥用,则往往变成 "为赋新词强说愁",轻则影响代码结构,重则埋下许多bug。Context 本质上是一种在 API 间树形嵌套调用时传递信号的机制。本文将从接口、派生、源码分析、使用等几个方面来逐一解析 Context。
木鸟杂记
2021/09/26
7250
golang从context源码领悟接口的设计
注:写帖子时go的版本是1.12.7 Context的github地址 go语言中实现一个interface不用像其他语言一样需要显示的声明实现接口。go语言只要实现了某interface的方法就可以做类型转换。go语言没有继承的概念,只有Embedding的概念。想深入学习这些用法,阅读源码是最好的方式.Context的源码非常推荐阅读,从中可以领悟出go语言接口设计的精髓。
lpxxn
2019/07/30
6340
Go context.WithCancel()的使用
WithCancel可以将一个Context包装为cancelCtx,并提供一个取消函数,调用这个取消函数,可以Cancel对应的Context Go语言context包-cancelCtx[1]
fliter
2023/09/05
2020
Go context.WithCancel()的使用
Golang 并发 与 context标准库
这篇文章将:介绍context工作机制;简单说明接口和结构体功能;通过简单Demo介绍外部API创建并使用context标准库;从源码角度分析context工作流程(不包括mutex的使用分析以及timerCtx计时源码)。
李海彬
2019/05/08
7960
Golang 并发 与 context标准库
Golang context 包入门
概述 Golang 的 context Package 提供了一种简洁又强大方式来管理 goroutine 的生命周期,同时提供了一种 Requst-Scope K-V Store。但是对于新手来说,Context 的概念不算非常的直观,这篇文章来带领大家了解一下 Context 包的基本作用和使用方法。 1. 包的引入 在 go1.7 及以上版本 context 包被正式列入官方库中,所以我们只需要import "context"就可以了,而在 go1.6 及以下版本,我们要 import "golang
李海彬
2018/03/26
1.1K0
【Go 并发控制】上下文 Context
在 Go 服务中,往往由一个独立的 goroutine 去处理一次请求,但在这个 goroutine 中,可能会开启别的 goroutine 去执行一些具体的事务,如数据库,RPC 等,同时,这一组 goroutine 可能还需要共同访问一些特殊的值,如用户 token, 请求过期时间等,当一个请求超时后,我们希望与此请求有关的所有 goroutine 都能快速退出,以回收系统资源。
JuneBao
2022/10/26
6250
【Go 并发控制】上下文 Context
Go 并发模式: context.Context 上下文详解
Package context 中定义了 Context 类型, 用于跨 API 或跨进程之间传递数据,包含 deadlines, cancellation signals, 以及其他 request-scoped values 。
一个会写诗的程序员
2022/05/13
1.3K1
Go 并发模式: context.Context 上下文详解
深入理解Golang之Context
这篇文章将介绍Golang并发编程中常用到一种编程模式:context。本文将从为什么需要context出发,深入了解context的实现原理,以及了解如何使用context。
KevinYan
2020/03/12
8680
Golang之context
当我们使用一些golang框架的时候,总能在框架中发现有个叫做context的东西。如果你之前了解过java的spring,那么你肯定也听说过其中有个牛逼的ApplicationContext。Context这个东西好像随时随地都在出现,在golang中也是非常重要的存在。今天我们就来看看这个神奇的Context。
LinkinStar
2022/09/01
6520
相关推荐
go context详解
更多 >
LV.1
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验