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(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。