go语言context包功能及操作使用详解
来源:脚本之家
时间:2022-12-31 15:57:28 447浏览 收藏
本篇文章主要是结合我之前面试的各种经历和实战开发中遇到的问题解决经验整理的,希望这篇《go语言context包功能及操作使用详解》对你有很大帮助!欢迎收藏,分享给更多的需要的朋友学习~
Context包到底是干嘛用的?
我们会在用到很多东西的时候都看到context的影子,比如gin框架,比如grpc,这东西到底是做啥的?
大家都在用,没几个知道这是干嘛的,知其然而不知其所以然
原理说白了就是:
- 当前协程取消了,可以通知所有由它创建的子协程退出
- 当前协程取消了,不会影响到创建它的父级协程的状态
- 扩展了额外的功能:超时取消、定时取消、可以和子协程共享数据
context原理
这就是context包的核心原理,链式传递context,基于context构造新的context
什么时候应该使用 Context?
- 每一个 RPC 调用都应该有超时退出的能力,这是比较合理的 API 设计
- 不仅仅 是超时,你还需要有能力去结束那些不再需要操作的行为
- context.Context 是 Go 标准的解决方案
- 任何函数可能被阻塞,或者需要很长时间来完成的,都应该有个 context.Context
如何创建 Context?
在 RPC 开始的时候,使用 context.Background()
有些人把在 main() 里记录一个 context.Background(),然后把这个放到服务器的某个变量里,然后请求来了后从这个变量里继承 context。这么做是不对的。直接每个请求,源自自己的 context.Background() 即可。
如果你没有 context,却需要调用一个 context 的函数的话,用 context.TODO()
如果某步操作需要自己的超时设置的话,给它一个独立的 sub-context(如前面的例子)
主协程通知有子协程,子协程又有多个子协程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | package main import ( "context" "fmt" "time" ) func main() { ctx, cancel := context.WithCancel(context.Background()) //缓冲通道预先放置10个消息 messages := make(chan int , 10 ) defer close(messages) for i := 0 ; i <p>运行结果如下</p> <p style= "text-align:center" ><img alt= "" src= "/uploads/20221231/167247451763afef9588f74.png" ></p> <p>可以看到,改成context包还是顺利的通过子协程退出了<br>主要修改了几个地方,再ctx向下传递</p> <p style= "text-align:center" ><img alt= "" src= "/uploads/20221231/167247451763afef95b7308.png" ></p> <p>基于上层context再构建当前层级的context</p> <p style= "text-align:center" ><img alt= "" src= "/uploads/20221231/167247451763afef95e6513.png" ></p> <p>监听context的退出信号,</p> <p style= "text-align:center" ><img alt= "" src= "/uploads/20221231/167247451863afef963971c.png" ></p> <p>这就是context包的核心原理,链式传递context,基于context构造新的context</p> <h2>context核心接口</h2> <pre class = "brush:groovy;" >type Context interface { Deadline() (deadline time.Time, ok bool) Done() <p> Deadline返回绑定当前context的任务被取消的截止时间;如果没有设定期限,将返回ok == false。</p> <p>Done 当绑定当前context的任务被取消时,将返回一个关闭的channel;如果当前context不会被取消,将返回nil。</p> <p>Err 如果Done返回的channel没有关闭,将返回nil;如果Done返回的channel已经关闭,将返回非空的值表示任务结束的原因。如果是context被取消,Err将返回Canceled;如果是context超时,Err将返回DeadlineExceeded。</p> <p>Value 返回context存储的键值对中当前key对应的值,如果没有对应的key,则返回nil。</p> <h3>emptyCtx结构体</h3> <p>实现了context接口,emptyCtx没有超时时间,不能取消,也不能存储额外信息,所以emptyCtx用来做根节点,一般用Background和TODO来初始化emptyCtx</p> <h3>Backgroud</h3> <p>通常用于主函数,初始化以及测试,作为顶层的context</p> <blockquote><p>context.Background()</p></blockquote> <h3>TODO</h3> <p>不确定使用什么用context的时候才会使用</p> <h3>valueCtx结构体</h3> <pre class = "brush:groovy;" >type valueCtx struct{ Context key, val interface {} } </pre> <p>valueCtx利用Context的变量来表示父节点context,所以当前context继承了父context的所有信息<br>valueCtx还可以存储键值。</p> <h3>WithValue向context添加值</h3> <p>可以向context添加键值</p> <pre class = "brush:groovy;" >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} } </pre> <p>添加键值会返回创建一个新的valueCtx子节点</p> <h3>Value向context取值</h3> <pre class = "brush:groovy;" >func (c *valueCtx) Value(key interface {}) interface {} { if c.key == key { return c.val } return c.Context.Value(key) } </pre> <p>可以用来获取当前context和所有的父节点存储的key</p> <p>如果当前的context不存在需要的key,会沿着context链向上寻找key对应的值,直到根节点</p> <h3>示例</h3> <pre class = "brush:groovy;" > package main import ( "context" "fmt" "time" ) func main() { ctx := context.WithValue(context.Background(), "name1" , "root1" ) //第一层 go func(parent context.Context) { ctx = context.WithValue(parent, "name2" , "root2" ) //第二层 go func(parent context.Context) { ctx = context.WithValue(parent, "name3" , "root3" ) //第三层 go func(parent context.Context) { //可以获取所有的父类的值 fmt.Println(ctx.Value( "name1" )) fmt.Println(ctx.Value( "name2" )) fmt.Println(ctx.Value( "name3" )) //不存在 fmt.Println(ctx.Value( "name4" )) }(ctx) }(ctx) }(ctx) time.Sleep( 1 * time.Second) fmt.Println( "end" ) } </pre> <p>运行结果</p> <p style= "text-align:center" ><img alt= "" src= "/uploads/20221231/167247451863afef96a80ba.png" ></p> <p>可以看到,子context是可以获取所有父级设置过的key</p> <h2>WithCancel可取消的context</h2> <p>用来创建一个可取消的context,返回一个context和一个CancelFunc,调用CancelFunc可以触发cancel操作。</p> <pre class = "brush:groovy;" > package main import ( "context" "fmt" "time" ) func main() { ctx, cancel := context.WithCancel(context.Background()) //第一层 go func(parent context.Context) { ctx, _ := context.WithCancel(parent) //第二层 go func(parent context.Context) { ctx, _ := context.WithCancel(parent) //第三层 go func(parent context.Context) { waitCancel(ctx, 3 ) }(ctx) waitCancel(ctx, 2 ) }(ctx) waitCancel(ctx, 1 ) }(ctx) // 主线程给的结束时间 time.Sleep( 2 * time.Second) cancel() // 调用取消context time.Sleep( 1 * time.Second) } func waitCancel(ctx context.Context, i int ) { for { time.Sleep(time.Second) select { case <p>结果:</p> <p style= "text-align:center" ><img alt= "" src= "/uploads/20221231/167247451863afef96d59f0.png" ></p> <h3>cancelCtx结构体</h3> <pre class = "brush:groovy;" >type cancelCtx struct { Context mu sync.Mutex done chan struct{} children map[canceler]struct{} err error } type canceler interface { cancel(removeFromParent bool, err error) Done() <-chan struct{} } </pre> <h2>WithDeadline-超时取消context</h2> <p>返回一个基于parent的可取消的context,并且过期时间deadline不晚于所设置时间d</p> <h2>WithTimeout-超时取消context</h2> <p>创建一个定时取消context,和WithDeadline差不多,WithTimeout是相对时间</p> <h3>timerCtx结构体</h3> <p>timerCtx是基于cancelCtx的context精英,是一种可以定时取消的context,过期时间的deadline不晚于所设置的时间d</p> <p>示例:</p> <pre class = "brush:groovy;" > package main import ( "context" "fmt" "time" ) func main() { // 设置超时时间 ctx, _ := context.WithTimeout(context.Background(), 2 *time.Second) //第一层 go func(parent context.Context) { ctx, _ := context.WithCancel(parent) //第二层 go func(parent context.Context) { ctx, _ := context.WithCancel(parent) //第三层 go func(parent context.Context) { waitCancel(ctx, 3 ) }(ctx) waitCancel(ctx, 2 ) }(ctx) waitCancel(ctx, 1 ) }(ctx) <p>运行结果:</p> <pre class = "brush:plain;" > 1 do 3 do 2 do 1 end 3 end 2 end </pre> <p>可以看到,虽然我们没有调用cancel方法, 5 秒后自动调用了,所有的子goroutine都已经收到停止信号</p> <h2>总结核心原理</h2> </pre></pre></pre> |
- Done方法返回一个channel
- 外部通过调用
- cancel方法会调用close(channel)
当调用close方法的时候,所有的channel再次从通道获取内容,会返回零值和false
1 | res,ok := <-done: |
- 过期自动取消,使用了time.AfterFunc方法,到时调用cancel方法
1 2 3 | c.timer = time.AfterFunc(dur, func() { c.cancel(true, DeadlineExceeded) }) |
今天关于《go语言context包功能及操作使用详解》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!
声明:本文转载于:脚本之家 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
-
164 收藏
-
208 收藏
-
500 收藏
-
334 收藏
-
391 收藏
最新阅读
更多>
-
248 收藏
-
355 收藏
-
462 收藏
-
260 收藏
-
103 收藏
-
297 收藏
-
463 收藏
-
218 收藏
-
133 收藏
-
214 收藏
-
444 收藏
-
463 收藏
课程推荐
更多>
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习
评论列表
-
- 可靠的大地
- 写的不错,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢博主分享文章内容!
- 2023-01-11 13:51:12
-
- 迷路的过客
- 这篇文章真及时,太细致了,很好,已收藏,关注老哥了!希望老哥能多写Golang相关的文章。
- 2023-01-05 21:32:42
-
- 传统的人生
- 太细致了,码住,感谢up主的这篇文章,我会继续支持!
- 2023-01-03 23:59:29
-
- 现实的酒窝
- 赞 👍👍,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢up主分享技术贴!
- 2023-01-01 19:41:15
-
- 超帅的小松鼠
- 这篇文章出现的刚刚好,太详细了,写的不错,码住,关注楼主了!希望楼主能多写Golang相关的文章。
- 2022-12-31 16:53:22