context 的简介
许多编程语言中都有上下文的概念,Go语言中也提供了这种特殊的数据结构 --context.Context,用来在多个goroutine之间设置超时时间、同步信号以及传递值。Context的操作是并发安全的,使用时不用担心数据并发安全的问题。
context 的使用
在web开发中,我们一般对每个请求都会开启一个goroutine 来处理;但是我们在处理请求的时候,不仅会去查询数据库,还会调用Http 或者RPC 服务,考虑到性能等因素,我们一般会并发的处理这些调用。所以在这个处理请求的 goroutine 中我们还会创建 n 个 goroutine。 这时我们就可以借助 context 来控制多个 goroutine。
- 在多个goroutine 之间共享值 一般我们对每次请求都会创建一个logId 来标记这次请求,在打印日志的时候会带上这个logId
const key ="logId"
func fn1(ctx context.Context){
if v:=ctx.Value(key);v!=nil{
logger.Info("logId",v)
}
}
func fn2(ctx context.Context){
if v:=ctx.Value(key);v!=nil{
logger.Info("logId",v)
}
}
func main(){
ctx := context.WithValue(context.Background(), key, "1234")
go fn1(ctx)
go fn2(ctx)
}
- timeout 控制 当我们请求下游服务时,通常对请求会有个超时的限制,当在限制的时间内没有返回,会抛出错误
func request(ctx context.Context,url string){
req,err:=http.NewRequestWithContext(ctx,'GET',url,nil)
if err!=nil {
logger.Error(err)
}
resp,err= http.DefaultClient.Do(req)
if err!=nil {
logger.Error(err)
}
// ...
}
func main(){
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
go request(ctx,"https://www.baidu.com")
}
- cancel 控制
比如在从数据库查询商品信息的时候抛出异常了,这时我们要返回给用户服务端异常,但是调用商品评论详情的API 还未结束,为了不浪费资源,我们需要取消这个操作。
// 商品详情
func goodsInfo(ctx context.Context) error{
result := make(chan interface{})
//...查询数据库
select {
case <-ctx.Done():
return ctx.Err()
case <-result:
return nil
}
}
// 商品评论
func goodsComment(ctx context.Context) error{
result := make(chan interface{})
//... 查询数据库
select {
case <-ctx.Done():
return ctx.Err()
case <-result:
return nil
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go func(){
if err:=goodsInfo(ctx);err!=nil{
cancel()
}
}()
go func(){
if err:=goodsComment(ctx);err!=nil{
cancel()
}
}()
}
小结
我们通过上面的例子,了解了在开发中使用context的几种场景。但是作为一个对技术有追求的人,难道不好奇context 怎么实现这些控制的吗,下一章节我们就对 context 的源码进行分析下。