• Golang使用context的一点随想


    前言

    这一篇是三巨头最后一篇了,前两篇介绍了channel,waitgroup,今天这篇来介绍一下context,相比于其他两种,我倒是更推荐context(上下文)这种控制goroutine的方法,为什呢,下面我就来详细的说一说吧。

    什么是Context(逻辑上下文)

    Context是一种链式的调用逻辑,一般是用来控制goroutine,例如说,控制goroutine的启动,停止,暂停,取消等等。 我这里举一个简单的例子来说明Context怎么用

    
    //WorkDir 定义一个遍历目录逻辑,假设这个目录很大,但是咱们需要随时把它结束掉
    func WorkDir(ctx context.Context, name string) {
        //假装这是一个大列表,很大很大那种
        filePathList := []string{........}
        for _, file := range(filePathList) {
            //假装这里处理一下目录, 打印一下目录
            fmt.Println(file)
            select {
            //检测到ctx发生了变化
            case <-ctx.Done():
                fmt.Println(name,"任务退出,骨灰都给你扬了")
                return
            //否则什么都不做
            default:
            }
        }
    }
    
    func main() {
        //定义一个waitcancel
        ctx, cancel := context.WithCancel(context.Background())
        //将ctx作为参数传入函数当中
        go WorkDir(ctx, "三秒之内撒了你")
        //让程序跑个几秒
        time.Sleep(3 * time.Second)
        fmt.Println("该杀任务了")
        //杀任务操作
        cancel()
        //没有监控输出,就表示停止
        time.Sleep(2 * time.Second)
        fmt.Println("任务让我杀了")
    }
    

    怎么样,是不是很简单,简单来说,ctx就是传递的信号,你给每一层传递的ctx,不管传的再多,都只有一个爹

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

    cancel是来通知goroutine结束的,当爹ctx执行了cancel之后,就表示,我死了,你们得和我一起被株连,大家一起完蛋。

    链式的意思就是,爹ctx是母体,它可以不断的继续下发产生多个子ctx,当cancel通知母体死亡的时候,子ctx也要跟着一起完蛋,所以链式就是,一荣俱荣,一关俱关的,cancel就是丧钟,调用就会释放所有的被感染(ctx下发)的goroutine。

    Context的几个重要方法

    WithCancel方法

    WithCancel方法的接口

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

    这里会返回一个ctx和cancel,ctx是一个可以被拷贝的context,这个context可以作为父节点继续向下传递,cancel则是通知ctx退出的信号,调用CancelFunc的时候,关闭c.done(),直接退出goroutine。 举个简单的例子,算了我偷个懒,用上面的例子,ctrl c ctrl v一波

    //WorkDir 定义一个遍历目录逻辑,假设这个目录很大,但是咱们需要随时把它结束掉
    func WorkDir(ctx context.Context, name string) {
        //假装这是一个大列表,很大很大那种
        filePathList := []string{........}
        for _, file := range(filePathList) {
            //假装这里处理一下目录, 打印一下目录
            fmt.Println(file)
            select {
            //检测到ctx发生了变化
            case <-ctx.Done():
                fmt.Println(name,"任务退出,骨灰都给你扬了")
                return
            //否则什么都不做
            default:
            }
        }
    }
    
    func main() {
        //定义一个waitcancel
        ctx, cancel := context.WithCancel(context.Background())
        //将ctx作为参数传入函数当中
        go WorkDir(ctx, "三秒之内撒了你")
        //让程序跑个几秒
        time.Sleep(3 * time.Second)
        fmt.Println("该杀任务了")
        //杀任务操作
        cancel()
        //没有监控输出,就表示停止
        time.Sleep(2 * time.Second)
        fmt.Println("任务让我杀了")
    }
    

    WithDeadline方法

    WithDeadline方法的接口

    func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
    

    这下机制出现了变化,cancel换成了deadline,参数也换了,换成了time,聪明的你应该想到了什么吧,这个函数其实是设置一个具体的死亡时间(deadline),当到达了指定的deadline时间之后,将所有的goroutine进行退出。咱们简单点儿,告诉你,就是到点儿退出。这里咱们照旧,举个例子,用例子来个详细说明。

    func WorkDir(ctx context.Context, name string) {
        //假装这是一个大列表,很大很大那种
        filePathList := []string{........}
        for _, file := range(filePathList) {
            //假装这里处理一下目录, 打印一下目录
            fmt.Println(file)
            select {
            //检测到ctx发生了变化
            case <-ctx.Done():
                fmt.Println(name,"任务超时了,骨灰都给你扬了")
                return
            case <-time.After(1 * time.Second):
                fmt.Println("我还在......")
            //否则什么都不做
            default:
            }
        }
    }
    
    func main() {
        //定义一个WithDeadline,这里定义了一个具体的时间点
    	ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10 *  time.Second))
        //将ctx作为参数传入函数当中
        go WorkDir(ctx, "从现在开始计时,10s之后撒了你")
        //让程序跑个几秒
        time.Sleep(3 * time.Second)
        fmt.Println("该杀任务了")
        //杀任务操作
        cancel()
        //没有监控输出,就表示停止
        time.Sleep(2 * time.Second)
        fmt.Println("任务让我杀了")
    }
    

    千万注意,后面还有个方法叫WithTimeout,它们两个说一样也不一样,WithTimeout是设置一个超时时间,WithDeadline是设定一个具体时间点,比如我上面那个,10s之内撒了goroutine,这个较为抽象,后面WithTimeout讲解的时候我会多费点功夫的。

    WithTimeout方法

    WithTimeout方法的接口

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

    对比一下上面那个WithDeadline,看到没,都需要传入一个时间变量,但是还是那句话,上面的WithDeadline需要的是具体时间,下面的WithTimeout就简单多了,就需要一个时间就行了,说简单点,上面的WithDeadline,要的是一个具体时间,例如 “2020-01-01 12:00:00”,

    time.Now().Add(5*time.Second)
    

    但是下面的WithTimeout,就很简单,就要一个超时时间就好了,例如5s

    timeout := 3 * time.Second
    

    类似crontab的用法。好了,继续举个例子

    func WorkDir(ctx context.Context, name string) {
        //假装这是一个大列表,很大很大那种
        filePathList := []string{........}
        for _, file := range(filePathList) {
            //假装这里处理一下目录, 打印一下目录
            fmt.Println(file)
            select {
            //检测到ctx发生了变化
            case <-ctx.Done():
                fmt.Println(name,"任务超时了,骨灰都给你扬了")
                return
            case <-time.After(1 * time.Second):
                fmt.Println("我还在......")
            //否则什么都不做
            default:
            }
        }
    }
    
    func main() {
        //定义一个WithDeadline,这里定义了一个具体的时间点
    	ctx, cancel:= context.WithTimeout(context.Background(), 5 * time.Second)
        //将ctx作为参数传入函数当中
        go WorkDir(ctx, "不管怎么样,没返回,5s之后撒了你")
        //让程序跑个几秒
        time.Sleep(20 * time.Second)
        fmt.Println("该杀任务了")
        //杀任务操作
        cancel()
        //没有监控输出,就表示停止
        time.Sleep(2 * time.Second)
        fmt.Println("任务让我杀了")
    }
    

    其实这两个用法都差不多,就是一个要求指定时间,一个随机时间,都是可以自己定义的.

    WithValue方法

    WithValue方法的接口

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

    这个看一下人家的传入,一个context,一个key, 一个接口interface,这明显就是一个key value类型的参数,这个方法的主要功能就是传递消息用,传递的元数据一般都是在子ctx中要使用到的,这个函数其实我用的不多,我参考了一下其他老哥的写法,总结了一下,具体的例子如下

    //定义一个key
    var key string = "keywork"
    
    func main() {
    	ctx, cancel := context.WithCancel(context.Background())
    	//附加值
    	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():
    		//取出值
    			fmt.Println(ctx.Value(key), "监控退出,停止了...")
    			return
    		default:
    		//取出值
    			fmt.Println(ctx.Value(key), "goroutine监控中...")
    			time.Sleep(2 * time.Second)
    		}
    	}
    }
    

    Context的使用注意事项

    1. Context 始终要以参数的形式进行传递,整个结构体是不太行的,当然,利用map存储进行设计管理还是可以的。
    2. Context 做参数你得永远把它放第一位,无论什么情况都切记
    3. Context 可以在多个goroutine中进行传递,无需担心,它是线程安全的。

    结尾

    两天终于把这个系列整完了,其实相当于自己复习了一下,也借鉴了一些别人的代码,value那个转手是在太多,我找不着原作者了,要是谁看到说是你写的,马上邮件我啊,我加上你的名字。

    下一步我要开个大坑,我要用Golang 整个大活儿,爬虫之类的我都不想写了,我现在打算用Golang复写我的一些安全工具,或者是写一个Elasticsearch相关的东西,期待吧!

  • 相关阅读:
    Java中抽象类和接口的区别
    servlet的转发与重定向
    JSP知识点
    过滤器与拦截器
    java关键字 super 和 this
    oracle 基础
    java 集合
    java 内部类
    java 数组详解
    图,深度优先遍历与广度优先遍历
  • 原文地址:https://www.cnblogs.com/Yemilice/p/13022317.html
Copyright © 2020-2023  润新知