• context源码


    Context接口有四个方法,

    Deadline方法是获取设置的截止时间的意思,第一个返回式是截止时间,到了这个时间点,Context会自动发起取消请求;第二个返回值ok==false时表示没有设置截止时间,如果需要取消的话,需要调用取消函数进行取消。

    Done方法返回一个只读的chan(我感觉不是只读可以写入零值),类型为struct{},我们在goroutine中,如果该方法返回的chan可以读取,则意味着parent context已经发起了取消请求(即已经调用了cancel函数),我们通过Done方法收到这个信号后,就应该做清理操作,然后退出goroutine,释放资源。

    Err方法返回取消的错误原因,因为什么Context被取消。

    Value方法获取该Context上绑定的值,是一个键值对,所以要通过一个Key才可以获取对应的值,这个值一般是线程安全的

    1 WithCancel   要想控制一个goroutine结束,可以通过cancel()来控制子context来结束该goroutine,WithCancel是context包下的一个函数,不是一个方法,可以直接执行,

    func main() {
        // WithCancel返回一个子context和一个可以终止该context的函数cancel,
        // context.Background()是一个空的Context,用于整个Context的根节点,
        ctx, cancel := context.WithCancel(context.Background())
        go func(ctx context.Context) {
            for {
                select {
                // ctx实现了Context接口,在下面调用cancel函数的时候,有close(c.done)关闭了Done中生成的chan,
                // 任何chan关闭后读取它时,都会返回里面类型的零值,这样Done中的chan就起到了关闭goroutine的作用,
                case <-ctx.Done():
                    fmt.Println("监控退出,停止了...")
                    return
                default:
                    fmt.Println("goroutine监控中...")
                    time.Sleep(2 * time.Second)
                }
            }
        }(ctx)
        time.Sleep(10 * time.Second)
        fmt.Println("可以了,通知监控停止")
        cancel()
        //为了检测监控过是否停止,如果没有监控输出,就表示停止了
        time.Sleep(5 * time.Second)
    }
    View Code

    WithCancel源码,

    // cancelCtx可以被取消,如果取消,则它的所有child也都被取消,
    type cancelCtx struct {
        Context
        mu       sync.Mutex            // protects following fields
        done     chan struct{}         // created lazily, closed by first cancel call
        children map[canceler]struct{} // set to nil by the first cancel call
        err      error                 // set to non-nil by the first cancel call
    }
    
    func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
        // 将传入的上下文包装成私有结构体 context.cancelCtx
        c := newCancelCtx(parent)
        // 会构建父子上下文之间的关联,当父上下文被取消时,子上下文也会被取消
        // 作用是在 parent 和 child 之间同步取消和结束的信号,
        // 保证在 parent 被取消时,child 也会收到对应的信号,不会发生状态不一致的问题
        // 核心是里面 p.children[child] = struct{}{} 的注册,
        propagateCancel(parent, &c)
        // 返回构建的子context的地址和匿名函数,
        // 新创建一个可取消的 context 节点,返回的 cancelFunc 函数会传入 true。这样做的结果是:
        // 当调用返回的 cancelFunc 时,会将这个 context 从它的父节点里“除名”,因为父节点可能有很多子节点,
        // 你自己取消了,所以我要和你断绝关系,对其他人没影响。
        return &c, func() { c.cancel(true, Canceled) }
    }
    
    // newCancelCtx returns an initialized cancelCtx.
    // 返回一个私有的cancelCtx,
    func newCancelCtx(parent Context) cancelCtx {
        return cancelCtx{Context: parent}
    }
    
    // propagateCancel arranges for child to be canceled when parent is.
    // propagateCancel当父上下文取消时,它色所有子上下文也会被取消,
    func propagateCancel(parent Context, child canceler) {
        // 取出父上下文的chan,
        done := parent.Done()
        // 若为空,说明是emptyCtx,不会被取消,
        if done == nil {
            return // parent is never canceled
        }
    
        select {
        case <-done:
            // // 若父上下文已经取消了,则调用cancel将当前子上下文及其所有的child都取消,
            child.cancel(false, parent.Err())
            return
        default:
        }
    
        if p, ok := parentCancelCtx(parent); ok {
            p.mu.Lock()
            if p.err != nil {
                // parent has already been canceled
                child.cancel(false, p.err)
            } else {
                // 将子上下文放入父上下文的children中,这样如果父上下文取消,则子上下文也会取消,
                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():
                }
            }()
        }
    }
    
    func parentCancelCtx(parent Context) (*cancelCtx, bool) {
        done := parent.Done()
        if done == closedchan || done == nil {
            return nil, false
        }
        // 判断parent是否封装了cancelctx,如果是,则判断是否重写了Done方法,否则直接返回nil false,
        // 这里的Value方法是不断调用Value实现的,
        p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
        if !ok {
            return nil, false
        }
        // 检查parent(即candelCtx)的done是否被重写了,如果ok为true,说明没有重写,返回true,
        // 将child加入parent的children列表中,等待parent释放取消信号
        // 如果ok为false,则重写了Done,需要另外判断,
        p.mu.Lock()
        ok = p.done == done
        p.mu.Unlock()
        if !ok {
            return nil, false
        }
        return p, true
    }
    
    // 如果parent里封装了cancelCtx,则最后必定会调用这个返回c,因为传入的key就是cancelCtxKey,
    func (c *cancelCtx) Value(key interface{}) interface{} {
        if key == &cancelCtxKey {
            return c
        }
        return c.Context.Value(key)
    }
    
    // cancel closes c.done, cancels each of c's children, and, if
    // removeFromParent is true, removes c from its parent's children.
    // cancel函数的三个作用
    // 1 关闭当前goroutine的chan,
    // 2 遍历当前goroutine的children,递归关闭它的所有子上下文
    // 3 removeFromParent为true时,将当前子上下文和它的父上下文断绝关系,
    func (c *cancelCtx) cancel(removeFromParent bool, err error) {
        if err == nil {
            panic("context: internal error: missing cancel error")
        }
        c.mu.Lock()
        if c.err != nil {
            c.mu.Unlock()
            return // already canceled
        }
        c.err = err
        if c.done == nil {
            c.done = closedchan
        } else {
            close(c.done)
        }
        for child := range c.children {
            // NOTE: acquiring the child's lock while holding parent's lock.
            child.cancel(false, err)
        }
        c.children = nil
        c.mu.Unlock()
        // removeFormParent只有第一次才为true,之后就没有必要了,为true的意思是当前的子上下文要和它的父上下文断绝关系
        // 即删除父上下文children中的子上下文,之后就为false了,没必要再删除了,因为当前子上下文中children中所有goroutine
        // 都会被删除,所以上面的child.cancel(false,err) 中传的是false,
        if removeFromParent {
            // 这里的c就是WithCancel中生成的c := newCancelCtx(parent),c.Context是它的父上下文,c是子上下文,
            // 这个函数就是要把父上下文中children中的子上下文删除 delete(p.children, child),
            // 这个是之前用propagateCancel(parent, &c)来注册的,
            removeChild(c.Context, c)
        }
    }
    View Code

    2  WithDeadline 会多传递一个截止时间参数,意味着到了这个时间点,会自动取消Context,当然我们也可以不等到这个时候,可以提前通过取消函数进行取消。 

    WithTimeout 表示是超时自动取消,是多少时间后自动取消Context的意思。

    3 利用WithValue方法可以给context附加值,

    package main
    import (
        "context"
        "fmt"
        "time"
    )
    var key string="name"
    func main() {
        ctx, cancel := context.WithCancel(context.Background())
        //附加值,将k-v对加到了context中,
        valueCtx:=context.WithValue(ctx,key,"【监控1】")
        go watch(valueCtx)
        time.Sleep(10 * time.Second)
        fmt.Println("可以了,通知监控停止")
        cancel()
        //为了检测监控过是否停止,如果没有监控输出,就表示停止了
        time.Sleep(5 * time.Second)
    }
    func watch(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():
                //取出值,Value是Context接口的方法,
                fmt.Println(ctx.Value(key),"监控退出,停止了...")
                return
            default:
                //取出值
                fmt.Println(ctx.Value(key),"goroutine监控中...")
                time.Sleep(2 * time.Second)
            }
        }
    }
    View Code

    参考: https://www.flysnow.org/2017/05/12/go-in-action-go-context.html

    context.Context的作用就是在不同 Goroutine 之间同步请求特定数据、取消信号以及处理请求的截止日期

    1 struct{}类型的chan只能接受struct{}{},其余的都不行,

    package main
    import (
        "fmt"
        "time"
    )
    func fun1(m chan int) {
        time.Sleep(5*time.Second)
        m <- 4
    }
    func fun2(m chan struct{})  {
        m <- struct{}{}
    }
    func main()  {
        type s struct {
            name string
        }
        c1 := make(chan int)
        c2 := make(chan struct{})
        //k := s{name: "tom"}
        // 由于c2是struc{}类型的channel,好像只能写入struct{}{},其它类型无法写入
        // https://www.jianshu.com/p/7f45d7989f3a
        //c2 <- struct{}{}
        go fun1(c1)
        go fun2(c2)
        select {
        case p := <- c1:
            fmt.Println("c1接受到了值", p, c1)
        case q := <- c2:
            fmt.Println("c2接收到了值", q, c2)
        }
    }
    View Code

    Done()的用法就是在context对象结束时返回一个channel,瞎写的,

    package main
    import (
        "context"
        "fmt"
        "time"
    )
    func main() {
        // 创建一个子节点的context,3秒后自动超时
        // WithTimeout()函数接受一个 Context 和超时时间作为参数,即context的过期时间,
        // 返回其子Context和取消函数cancel
        ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
        go watch(ctx, "监控1")
        go watch(ctx, "监控2")
        fmt.Println("现在开始等待8秒,time=", time.Now().Unix())
        time.Sleep(8 * time.Second)
        fmt.Println("等待8秒结束,准备调用cancel()函数,发现两个子协程已经结束了,time=", time.Now().Unix())
        // 若不调用cancel函数,到了原先创建Contetx时的超时时间,它也会自动调用cancel()函数,
        // 即会往子Context的Done通道发送消息
        cancel()
    }
    // 单独的监控协程
    func watch(ctx context.Context, name string) {
        for {
            select {
            // Done()函数 当ctx返回一个channel,若该channel为空,则
            case k := <-ctx.Done():
                fmt.Println("-----", k)
                fmt.Println(name, "收到信号,监控退出,time=", time.Now().Unix())
                return
            default:
                fmt.Println(name, "goroutine监控中,time=", time.Now().Unix())
                time.Sleep(1 * time.Second)
            }
        }
    }
    // 特别注意Done()返回的类型是struct{}类型的chan,这种chan只能在接收struct{}{}或close(chan)的时候才能结束读取,
    // 也即只有这两种情况才有返回值,
    //func (c *cancelCtx) Done() <-chan struct{} {
    //    c.mu.Lock()
    //    if c.done == nil {
    //        c.done = make(chan struct{})
    //    }
    //    d := c.done
    //    c.mu.Unlock()
    //    return d
    //}
    View Code

    https://www.yuque.com/baxiang/go/oy5l3k

    https://zhuanlan.zhihu.com/p/165621566

    https://blog.csdn.net/ruanhao1203/article/details/80044026

    https://studygolang.com/articles/17796

    https://zhuanlan.zhihu.com/p/174532117

    http://interview.wzcu.com/Golang/%E5%9F%BA%E7%A1%80.html

    https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-context/

    https://zhuanlan.zhihu.com/p/91559562

    https://zhuanlan.zhihu.com/p/68792989

  • 相关阅读:
    excel 2003系列
    DataTab转换XML XML转换DataTable 的类[转]
    全角转半角与半角转全角
    Day2
    Day6 && Day7图论
    Day1
    Android为何以及如何保存Fragment实例
    Android应用的本地化及知识拓展之配置修饰符
    Leetcode NO.136 Single Number 只出现一次的数字
    经典排序算法(四) —— Quick Sort 快速排序
  • 原文地址:https://www.cnblogs.com/xxswkl/p/14198554.html
Copyright © 2020-2023  润新知