Golang中Context使用的一点随想 前言 这一篇是三巨头最后一篇了,前两篇介绍了channel,waitgroup,今天这篇来介绍一下context,相比于其他两种,我倒是更推荐context(上下文)这种控制goroutine的方法,为什呢,下面我就来详细的说一说吧。
什么是Context(逻辑上下文) Context是一种链式的调用逻辑,一般是用来控制goroutine,例如说,控制goroutine的启动,停止,暂停,取消等等。 我这里举一个简单的例子来说明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 COPY func WorkDir (ctx context.Context, name string ) { filePathList := []string {........} for _, file := range (filePathList) { fmt.Println(file) select { case <-ctx.Done(): fmt.Println(name,"任务退出,骨灰都给你扬了" ) return default : } } } func main () { ctx, cancel := context.WithCancel(context.Background()) go WorkDir(ctx, "三秒之内撒了你" ) time.Sleep(3 * time.Second) fmt.Println("该杀任务了" ) cancel() time.Sleep(2 * time.Second) fmt.Println("任务让我杀了" ) }
怎么样,是不是很简单,简单来说,ctx就是传递的信号,你给每一层传递的ctx,不管传的再多,都只有一个爹
1 COPY ctx, cancel := context.WithCancel(context.Background())
cancel是来通知goroutine结束的,当爹ctx执行了cancel之后,就表示,我死了,你们得和我一起被株连,大家一起完蛋。
链式 的意思就是,爹ctx是母体,它可以不断的继续下发产生多个子ctx,当cancel通知母体死亡的时候,子ctx也要跟着一起完蛋,所以链式就是,一荣俱荣,一关俱关的,cancel就是丧钟,调用就会释放所有的被感染(ctx下发)的goroutine。
Context的几个重要方法 WithCancel方法 WithCancel方法的接口
1 COPY func WithCancel (parent Context) (ctx Context, cancel CancelFunc)
这里会返回一个ctx和cancel,ctx是一个可以被拷贝的context,这个context可以作为父节点继续向下传递,cancel则是通知ctx退出的信号,调用CancelFunc的时候,关闭c.done(),直接退出goroutine。 举个简单的例子,算了我偷个懒,用上面的例子,ctrl c ctrl v一波
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 COPY func WorkDir (ctx context.Context, name string ) { filePathList := []string {........} for _, file := range (filePathList) { fmt.Println(file) select { case <-ctx.Done(): fmt.Println(name,"任务退出,骨灰都给你扬了" ) return default : } } } func main () { ctx, cancel := context.WithCancel(context.Background()) go WorkDir(ctx, "三秒之内撒了你" ) time.Sleep(3 * time.Second) fmt.Println("该杀任务了" ) cancel() time.Sleep(2 * time.Second) fmt.Println("任务让我杀了" ) }
WithDeadline方法 WithDeadline方法的接口
1 COPY func WithDeadline (parent Context, deadline time.Time) (Context, CancelFunc)
这下机制出现了变化,cancel换成了deadline,参数也换了,换成了time,聪明的你应该想到了什么吧,这个函数其实是设置一个具体的死亡时间(deadline),当到达了指定的deadline时间之后,将所有的goroutine进行退出。咱们简单点儿,告诉你,就是到点儿退出。这里咱们照旧,举个例子,用例子来个详细说明。
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 COPY func WorkDir (ctx context.Context, name string ) { filePathList := []string {........} for _, file := range (filePathList) { fmt.Println(file) select { case <-ctx.Done(): fmt.Println(name,"任务超时了,骨灰都给你扬了" ) return case <-time.After(1 * time.Second): fmt.Println("我还在......" ) default : } } } func main () { ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10 * time.Second)) 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方法的接口
1 COPY func WithTimeout (parent Context, timeout time.Duration) (Context, CancelFunc)
对比一下上面那个WithDeadline,看到没,都需要传入一个时间变量,但是还是那句话,上面的WithDeadline需要的是具体时间,下面的WithTimeout就简单多了,就需要一个时间就行了,说简单点,上面的WithDeadline,要的是一个具体时间,例如 “2020-01-01 12:00:00”,
1 COPY time.Now().Add(5 *time.Second)
但是下面的WithTimeout,就很简单,就要一个超时时间就好了,例如5s
1 COPY timeout := 3 * time.Second
类似crontab的用法。好了,继续举个例子
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 COPY func WorkDir (ctx context.Context, name string ) { filePathList := []string {........} for _, file := range (filePathList) { fmt.Println(file) select { case <-ctx.Done(): fmt.Println(name,"任务超时了,骨灰都给你扬了" ) return case <-time.After(1 * time.Second): fmt.Println("我还在......" ) default : } } } func main () { ctx, cancel:= context.WithTimeout(context.Background(), 5 * time.Second) go WorkDir(ctx, "不管怎么样,没返回,5s之后撒了你" ) time.Sleep(20 * time.Second) fmt.Println("该杀任务了" ) cancel() time.Sleep(2 * time.Second) fmt.Println("任务让我杀了" ) }
其实这两个用法都差不多,就是一个要求指定时间,一个随机时间,都是可以自己定义的.
WithValue方法 WithValue方法的接口
1 COPY func WithValue (parent Context, key interface {}, val interface {}) (Context)
这个看一下人家的传入,一个context,一个key, 一个接口interface,这明显就是一个key value类型的参数,这个方法的主要功能就是传递消息用,传递的元数据一般都是在子ctx中要使用到的,这个函数其实我用的不多,我参考了一下其他老哥的写法,总结了一下,具体的例子如下
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 COPY 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的使用注意事项
Context 始终要以参数的形式进行传递,整个结构体是不太行的,当然,利用map存储进行设计管理还是可以的。
Context 做参数你得永远把它放第一位,无论什么情况都切记
Context 可以在多个goroutine中进行传递,无需担心,它是线程安全的。
结尾 两天终于把这个系列整完了,其实相当于自己复习了一下,也借鉴了一些别人的代码,value那个转手是在太多,我找不着原作者了,要是谁看到说是你写的,马上邮件我啊,我加上你的名字。
下一步我要开个大坑,我要用Golang 整个大活儿,爬虫之类的我都不想写了,我现在打算用Golang复写我的一些安全工具,或者是写一个Elasticsearch相关的东西,期待吧!