golang通过context控制并发的应用场景实现
golang里出现多goroutine的场景很常见,最常用的两种方式就是WaitGroup和Context,今天我们了解一下Context的应用场景
使用场景
场景一:多goroutine执行超时通知
并发执行的业务中最常见的就是有协程执行超时,如果不做超时处理就会出现一个僵尸进程,这累计的多了就会有一阵手忙脚乱了,所以我们要在源头上就避免它们
看下面这个示例:
packagemain import( "context" "fmt" "time" ) /** 同一个content可以控制多个goroutine,确保线程可控,而不是每新建一个goroutine就要有一个chan去通知他关闭 有了他代码更加简洁 */ funcmain(){ fmt.Println("rundemo\n\n\n") demo() } funcdemo(){ ctx,cancel:=context.WithTimeout(context.Background(),9*time.Second) gowatch(ctx,"[线程1]") gowatch(ctx,"[线程2]") gowatch(ctx,"[线程3]") index:=0 for{ index++ fmt.Printf("%d秒过去了\n",index) time.Sleep(1*time.Second) ifindex>10{ break } } fmt.Println("通知停止监控") //其实此时已经超时,协程已经提前退出 cancel() //防止主进程提前退出 time.Sleep(3*time.Second) fmt.Println("done") } funcwatch(ctxcontext.Context,namestring){ for{ select{ case<-ctx.Done(): fmt.Printf("%s监控退出,停止了...\n",name) return default: fmt.Printf("%sgoroutine监控中...\n",name) time.Sleep(2*time.Second) } } }
使用context.WithTimeout()给文本流设置一个时间上限,结合for+select去接收消息.当执行超时,或手动关闭都会给<-ctx.Done()发送消息,而且所有使用同一个context都会收到这个通知,免去了一个一个通知的繁琐代码
场景二:类似web服务器中的session
比如在php中(没用swoole扩展),一个请求进来,从$_REQUEST$_SERVER能获取到的是有关这一条请求的所有信息,哪怕是使用全局变量也是给这一个请求来服务的,是线程安全的
但是golang就不一样了,因为程序本身就能起一个websever,因此就不能随便使用全局变量了,不然就是内存泄露警告.但是实际业务当中需要有一个类似session的东西来承载单次请求的信息,举一个具体的例子就是:给每次请求加一个uniqueID该如何处理?有了这个uniqueID,请求的所有日志都能带上它,这样排查问题的时候方便追踪一次请求发生了什么
如下:
funcdemo2(){ pCtx,pCancel:=context.WithCancel(context.Background()) pCtx=context.WithValue(pCtx,"parentKey","parentVale") gowatch(pCtx,"[父进程1]") gowatch(pCtx,"[父进程2]") cCtx,cCancel:=context.WithCancel(pCtx) gowatch(cCtx,"[子进程1]") gowatch(cCtx,"[子进程2]") fmt.Println(pCtx.Value("parentKey")) fmt.Println(cCtx.Value("parentKey")) time.Sleep(10*time.Second) fmt.Println("子进程关闭") cCancel() time.Sleep(5*time.Second) fmt.Println("父进程关闭") pCancel() time.Sleep(3*time.Second) fmt.Println("done") }
最开始的context.WithCancel(context.Background())中context.Background()就是一个新建的context,利用context能继承的特性,可以将自己的程序构建出一个context树,context执行cancel()将影响到当前context和子context,不会影响到父级.
同时context.WithValue也会给context带上自定义的值,这样uniqueID就能轻松的传递了下去,而不是一层层的传递参数,改func什么的
对于context很值得参考的应用有:
- Gin
- logrus
Context相关func和接口
继承context需要实现如下四个接口
typeContextinterface{ Deadline()(deadlinetime.Time,okbool) Done()<-chanstruct{} Err()error Value(keyinterface{})interface{} }
当使用的时候不需要实现接口,因为官方包里已经基于emptyCtx实现了一个,调用方法有
var( background=new(emptyCtx) todo=new(emptyCtx) ) //这个是最初始的ctx,之后的子ctx都是继承自它 funcBackground()Context{ returnbackground } //不清楚context要干嘛,但是就得有一个ctx的用这个 funcTODO()Context{ returntodo }
继承用的函数
funcWithCancel(parentContext)(ctxContext,cancelCancelFunc) funcWithDeadline(parentContext,deadlinetime.Time)(Context,CancelFunc) funcWithTimeout(parentContext,timeouttime.Duration)(Context,CancelFunc) funcWithValue(parentContext,key,valinterface{})Context
- WithCancel返回一个带cancel函数的ctx,
- WithDeadline在到达指定时间时自动执行cancel()
- WithTimeout是WithDeadline的壳子,区别就是这个函数是多少时间过后执行cancel
funcWithTimeout(parentContext,timeouttime.Duration)(Context,CancelFunc){ returnWithDeadline(parent,time.Now().Add(timeout)) }
WithValue继承父类ctx时顺便带上一个值
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。