已发表的技术专栏
0 grpc-go、protobuf、multus-cni 技术专栏 总入口
4 grpc、oauth2、openssl、双向认证、单向认证等专栏文章目录)
本小节我们简单分析一下golang语言中的cancel函数,
为什么调用cancel函数后,ctx.Done()就不再阻塞了呢? |
在grpc-go/examples/features/cancellation/client/main.go文件中,以下面的语句作为分析入口:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
cancel是context.WithTimetout返回的函数;
因此,进入WithTimeout方法内部:
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
其中,parent就是context.Background();
进入WithDeadline方法里:
1.func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
2. if parent == nil {
3. panic("cannot create context from nil parent")
4. }
5. if cur, ok := parent.Deadline(); ok && cur.Before(d) {
6. // The current deadline is already sooner than the new one.
7. return WithCancel(parent)
8. }
9. c := &timerCtx{
10. cancelCtx: newCancelCtx(parent),
11. deadline: d,
12. }
13. propagateCancel(parent, c)
14. dur := time.Until(d)
15. if dur <= 0 {
16. c.cancel(true, DeadlineExceeded) // deadline has already passed
17. return c, func() { c.cancel(false, Canceled) }
18. }
19. c.mu.Lock()
20. defer c.mu.Unlock()
21. if c.err == nil {
22. c.timer = time.AfterFunc(dur, func() {
23. c.cancel(true, DeadlineExceeded)
24. })
25. }
26. return c, func() { c.cancel(true, Canceled) }
27.}
其实,我们不需要了解这个方法的具体执行逻辑;
观察这个函数,我们会发现,返回的形式,有两种类型
- 一个是返回WithCancel函数
- 一个是返回c,以及一个匿名函数func() { c.cancel(XXXX, YYYY) }
其中,WithCancel函数内部,最终返回的也是c,以及一个匿名函数func() { c.cancel(XXXX, YYYY) }
而这个匿名函数,最终就是赋值给cancel的;
当客户端主动调用cancel函数时,其实就是调用这个匿名函数;最终调用的是c.cancel方法,
因此,进入c.cancel方法里:
1.func (c *timerCtx) cancel(removeFromParent bool, err error) {
2. c.cancelCtx.cancel(false, err)
3. if removeFromParent {
4. // Remove this timerCtx from its parent cancelCtx's children.
5. removeChild(c.cancelCtx.Context, c)
6. }
7. c.mu.Lock()
8. if c.timer != nil {
9. c.timer.Stop()
10. c.timer = nil
11. }
12. c.mu.Unlock()
13.}
我们只关心第2行:继续进入cancel方法里:
1.func (c *cancelCtx) cancel(removeFromParent bool, err error) {
2. //---省略掉不相关代码
3. if c.done == nil {
4. c.done = closedchan
5. } else {
6. close(c.done)
7. }
8. //---省略掉不相关代码
9.}
只关心第6行:执行close(c.done),也就是关闭通道;
我们知道,
一个通道关闭后,还是可以从通道里读取到默认数据的,也就是说c.done通道就会解除阻塞状态; |
即grpc-go/stream.go文件中的newClientStream函数里的 “case <- ctx.Done()” 就会解除阻塞,从而触
发客户端流cs调用finish的处理逻辑。
总结: |
调用cancel,其实最终调用的是golang内置的函数close,是用来关闭通道的; |
从而达到的效果就是:通道解除阻塞了。
下一篇文章
截止时间相关介绍
点击下面的图片,返回到专栏大纲 |