golang mysql context_golang Context 源码

前言

context 是一个很常用的功能,并且源码也不那么难。

我觉得是比较适合初次入手的,整个代码564行,去掉注释,估计200-300行。并且没有特别涉及到底层的代码。

适合做一个好的开头,如果一开始就读GC源码,感觉就是直接能放弃~

用途上下文 context.Context 是用来设置截止日期、同步信号,传递请求相关值的结构体。

它适合在多 goroutine 的情况下,管理 goroutine 直接的关系。

如果没有这个模块,我们在不断 go goroutine 时,goroutine 会失控,比如父线程其实已经终止了,但是这个父线程启动的子线程可能还在占用资源。

比较直接的方式,起启动一个协程池,去标记管理启动的 goroutine,但是这种就比较偏自由的实现了,实现方案和代码能力强相关。

golang源码也提供了解决方案,context.Context 就是一个标准的,管理 goroutine 之间的关系模块。

他的底层是通过 channel 去传递管理信号。例如,父 goroutine 可以传递一个close信号,子 goroutine 通过管道监听这个消息,如果收到就自动关闭。

提供了下面4中标准方法:WithCancel

WithDeadline

WithTimeout

WithValue

其中 WithDeadline 和 WithTimeout其实一个实现,只是用法有差别

源码里面可以看出 WithTimeout 是把 WithDeadline 包装了一层

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {

return WithDeadline(parent, time.Now().Add(timeout))

}

前面3个方法是主要通过通道去管理 goroutine , WithValue 这是可以在父子 goroutine 共享内容。

使用场景也非常广,例如 gin 这种web框架,就包装了Context,并且作为请求处理函数的第一个参数,供开发者使用。

r.GET("/ping", func(c *gin.Context) {

c.JSON(200, gin.H{

"message": "pong",

})

})

gin.Context 就是对 context.Context 的一个封装。

实现

这个源码很早看过,当时看着别人的源码解读,很快有个大致的思路,但是真正在我要写这篇文章,自己描述时,发现其实理解不到位。

所以这个源码,我打算通过如果一步步自从自己最简单的实现去剖析源码。

WithCancel

一个最简单的应用,

func main() {

ctx, cancel := context.WithCancel(context.Background())

for i := 0; i < 3; i++ {

go func(ctx context.Context, index int) {

select {

case

fmt.Println(index)

}

}(ctx, i)

}

cancel()

time.Sleep(1 * time.Second)

}

当主 goroutine 调用 cancel() 后,子 goroutine 会收到

假如我们想自己实现这个逻辑,怎么实现

最简版

func main() {

var ctx = make(chan int)

for i := 0; i < 3; i++ {

go func(ctx chan int, index int) {

select {

case

fmt.Println(index)

}

}(ctx, i)

}

close(ctx)

time.Sleep(1 * time.Second)

}

上述代码通过 chan 可以实现一样的功能,并且 context 底层也是通过这种方式去实现的。

核心其实就是关闭通道 close 。

更进一步

我们更进一步,实现的更像 context 一点,主要是按照 context 思路,实现了Done 和 cancel 两个功能。

type Cont struct {

mu sync.Mutex

done chan struct{} // 这个就是上面例子里面的 chan 用于接收关闭信号, created lazily, closed by first cancel call}

func (c *Cont) Done()

c.mu.Lock()

// created lazily,如果有 chan 使用已有的,如果没有,创建一个,并且其他也会使用这个 if c.done == nil {

c.done = make(chan struct{})

}

d := c.done

c.mu.Unlock()

return d

}

func (c *Cont) cancel() {

// 在关闭通道时,如果压根没有子 goroutine ,就直接赋值一个已经关闭的通道,这个处理很有技巧 // 这段代码中,其实只有在执行 ctx.Done() 时,才会去 created lazily 方式初始化一个 chan // 因为 goroutine 不确定执行顺序,可能在子 goroutine 还没有执行时,父就调用了 cancel 这个时候 c.done == nil // 所以这里就给 c.done 复制了一个关闭的 chan ,这样在子 goroutine 调用 ctx.Done() 时,就会马上收到一个关闭信号 c.mu.Lock()

if c.done == nil {

c.done = closedchan

} else {

close(c.done)

}

c.mu.Unlock()

}

// 初始化一个关闭的 chanvar closedchan = make(chan struct{})

func init(){

close(closedchan)

}

func main() {

var ctx = Cont{}

for i := 0; i < 3; i++ {

go func(ctx *Cont, index int) {

select {

case

fmt.Println(index)

}

}(&ctx, i)

}

ctx.cancel()

time.Sleep(1 * time.Second)

}

基于上面简单的实现,大家在看源码,就能稍微清晰一点

WithDeadline

在看下 context.WithDeadline 的一个应用

func main() {

ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(1* time.Second))

for i := 0; i < 3; i++ {

go func(ctx context.Context, index int) {

select {

case

fmt.Println(index)

}

}(ctx, i)

}

time.Sleep(3 * time.Second)

cancel()

time.Sleep(1 * time.Second)

}

设置了一个1秒的超时,子线程会在1秒后自动关闭

这个也是在前面自己实现的基础上,加上了 time.AfterFunc ,下面是参考源码的一个简单实现

type Cont struct {

mu sync.Mutex

done chan struct{}

timer *time.Timer

}

func (c *Cont) Done()

c.mu.Lock()

if c.done == nil {

c.done = make(chan struct{})

}

d := c.done

c.mu.Unlock()

return d

}

func (c *Cont) cancel() {

c.mu.lock()

if c.done == nil {

c.done = closedchan

} else {

close(c.done)

}

// 主动关闭计时器如果有的话 if c.timer != nil {

c.timer.Stop()

c.timer = nil

}

c.mu.Unlock()

}

var closedchan = make(chan struct{})

func init(){

close(closedchan)

}

// 这里主要加了 time.AfterFunc 做了一个超时自动关闭逻辑func Deadline(c *Cont, d time.Time) *Cont {

dur := time.Until(d)

c.mu.Lock()

defer c.mu.Unlock()

c.timer = time.AfterFunc(dur, func() {

c.cancel()

})

return c

}

func main() {

var ctx = Deadline(&Cont{}, time.Now().Add(1*time.Second))

for i := 0; i < 3; i++ {

go func(ctx *Cont, index int) {

select {

case

fmt.Println(index)

}

}(ctx, i)

}

time.Sleep(3 * time.Second)

}

WithValue

context.WithValue 这个相对前面,简单一点,源码也不多,可以直接看

主要是下面2段源码

func WithValue(parent Context, key, val interface{}) Context {

...

return &valueCtx{parent, key, val}

}

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)

}

深入研究

其实基于上面的实现,去源码对照阅读,发现源码里面还有一些父子关系的处理,我们并没有用到,例如 propagateCancel 函数。

上面的例子,也只是用上了一层父子关系逻辑,下面我们继续深入看看,多层关系的实现,网上这块用法其实比较少,我们先看看怎么实现。

多个层级的例子

func hander(ctx context.Context, name string) {

for i := 0; i < 3; i++ {

go func(ctx context.Context, index int) {

select {

case

fmt.Println(name, index)

}

}(ctx, i)

}

}

func main() {

ctx, cancel := context.WithCancel(context.Background())

actx, acancel := context.WithCancel(ctx)

bctx, bcancel := context.WithCancel(ctx)

cctx, _ := context.WithCancel(ctx)

hander(actx, "a hander")

hander(bctx, "b hander")

hander(cctx, "c hander")

fmt.Println("close a hander")

acancel()

time.Sleep(2 * time.Second)

fmt.Println("close b hander")

bcancel()

time.Sleep(2 * time.Second)

fmt.Println("close other hander")

cancel()

time.Sleep(1 * time.Second)

}

输出

close a hander

a hander 0

a hander 1

a hander 2

close b hander

b hander 2

b hander 1

b hander 0

close other hander

c hander 2

c hander 1

c hander 0

这里我们看源码

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {

...

// 实例化一个子 context c := newCancelCtx(parent)

// 父和子绑定关系 propagateCancel(parent, &c)

return &c, func() { c.cancel(true, Canceled) }

}

其中 newCancelCtx 这个逻辑,我们上面简单的实现就是参考这个去做的

所以可以重点看下 propagateCancel

func propagateCancel(parent Context, child canceler) {

...

if p, ok := parentCancelCtx(parent); ok {

// 当 child 的继承链包含可以取消的上下文时,会判断 parent 是否已经触发了取消信号; p.mu.Lock()

if p.err != nil {

// 如果已经被取消,child 会立刻被取消; child.cancel(false, p.err)

} else {

if p.children == nil {

p.children = make(map[canceler]struct{})

}

// 如果没有被取消,child 会被加入 parent 的 children 列表中,等待 parent 释放取消信号; p.children[child] = struct{}{}

}

p.mu.Unlock()

} else {

atomic.AddInt32(&goroutines, +1)

// 运行一个新的 Goroutine 同时监听 parent.Done() 和 child.Done() 两个 Channel // 在 parent.Done() 关闭时调用 child.cancel 取消子上下文; go func() {

select {

case

child.cancel(false, parent.Err())

case

}

}()

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值