![27e07925e4fe0a17d92b735d0588a016.png](https://i-blog.csdnimg.cn/blog_migrate/80211ca7d7e6903bf34d1abda119766f.jpeg)
视频信息
How to correctly use package context
by Jack Lindamood
at Golang UK Conf. 2017
视频:https://www.youtube.com/watch?v=-_B5uQ4UGi0
博文:https://medium.com/@cep21/how-to-correctly-use-context-context-in-go-1-7-8f2c0fafdf39
为什么需要 Context
- 每一个长请求都应该有个超时限制
- 需要在调用中传递这个超时
- 比如开始处理请求的时候我们说是 3 秒钟超时
- 那么在函数调用中间,这个超时还剩多少时间了?
- 需要在什么地方存储这个信息,这样请求处理中间可以停止
如果进一步考虑。
![51241f0a015159621506a17c6a556359.png](https://i-blog.csdnimg.cn/blog_migrate/d5f1f9ab509c9db920c8088698f9a75a.jpeg)
如上图这样的 RPC 调用,开始调用 RPC 1 后,里面分别调用了 RPC 2, RPC 3, RPC 4,等所有 RPC 用成功后,返回结果。
这是正常的方式,但是如果 RPC 2 调用失败了会发生什么?
![991ad332d04ae9a7c0b00c79b07ec423.png](https://i-blog.csdnimg.cn/blog_migrate/0d3af81576b1ceb7ad816cb65b6660da.jpeg)
RPC 2 失败后,如果没有 Context 的存在,那么我们可能依旧会等所有的 RPC 执行完毕,但是由于 RPC 2 败了,所以其实其它的 RPC 结果意义不大了,我们依旧需要给用户返回错误。因此我们白白的浪费了 10ms,完全没必要去等待其它 RPC 执行完毕。
那如果我们在 RPC 2 失败后,就直接给用户返回失败呢?
![99b47acc411b1935953b4afc7304faf4.png](https://i-blog.csdnimg.cn/blog_migrate/2feddc3a3cb2831c66ba2b71ab433bf6.jpeg)
用户是在 30ms 的位置收到了错误消息,可是 RPC 3 和 RPC 4 依然在没意义的运行,还在浪费计算和IO资源。
![7fdb51374edb64248026240624492df4.png](https://i-blog.csdnimg.cn/blog_migrate/6430d34a93254804b8f8d592876ddaed.jpeg)
所以理想状态应该是如上图,当 RPC 2 出错后,除了返回用户错误信息外,我们也应该有某种方式可以通知 RPC 3 和 RPC 4,让他们也停止运行,不再浪费资源。
所以解决方案就是:
- 用信号的方式来通知请求该停了
- 包含一些关于什么时间请求可能会结束的提示(超时)
- 用 channel 来通知请求结束了
那干脆让我们把变量也扔那吧。
- 在 Go 中没有线程/go routine 变量
- 其实挺合理的,因为这样就会让 goroutine 互相产生依赖
- 非常容易被滥用
Context 实现细节
context.Context:
- 是不可变的(immutable)树节点
- Cancel 一个节点,会连带 Cancel 其所有子节点 (从上到下)
- Context values 是一个节点
- Value 查找是回溯树的方式 (从下到上)
示例 Context 链
完整代码:https://play.golang.org/p/ddpofBV1QS
package mainfunc tree() { ctx1 := context.Background() ctx2, _ := context.WithCancel(ctx1) ctx3, _ := context.WithTimeout(ctx2, time.Second * 5) ctx4, _ := context.WithTimeout(ctx3, time.Second * 3) ctx5, _ := context.WithTimeout(ctx3, time.Second * 6) ctx6 := context.WithValue(ctx5, "userID