介绍
本文主要是一个简单的入门使用,具体细节后续文章会详细说明
从名字来看就是一个上下文的意思,通常上下文会跟随你的整个流水线而走,这样方便在每个进程或者协程中都能拿到一些重要信息。比如java的Dubbo框架里面当执行一个Rpc调用时可以在上下文RpcContext存放trace_id,服务提供方接收到后,如果需要它就可以直接在上下文中拿到
在go语言的http库里面,发送一个request请求也可以携带上下文。接收方可以通过request拿到,这样方便做一些超时处理
go语言中的context除了可以传递value 还提供了一些取消,超时操作。如果程序报错需要中断后续操作,请求调用超出了最大响应时间就可以用得上。
一般上下文信息不建议存放在结构体中,通常放到请求参数的第一位,以ctx命名,如果暂时还不知道需要什么类型的上下文,可以使用context包提供的TODO,其实就是一个空context
官方提供了Context接口,有两个默认实现,
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
context我们都把它理解为一个节点树,没有特殊的情况,我们就使用background、todo 这两个作为顶级context,基于这两个context不断衍生出子context,就成了一个context树
官方给我们提供了四个派生函数,都是通过一个父context,派生出一个子context,可以想象经过多重派生之后就成了树
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val any) Context
首先看最简单的 WithValue 可以在不同的context之间传递key-value,下面例子是打印调用来源
WithValue
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx := context.Background()
ctx = context.WithValue(ctx, "requestSrc", "main")
go a(ctx)
time.Sleep(time.Second)
}
func a(ctx context.Context) {
fmt.Println("a方法调用来源", ctx.Value("requestSrc"))
ctx = context.WithValue(ctx, "requestSrc", "a")
go b(ctx)
}
func b(ctx context.Context) {
fmt.Println("b方法调用来源", ctx.Value("requestSrc"))
}
执行结果
a方法调用来源 main
b方法调用来源 a
Process finished with the exit code 0
WithCancel
如果一个线程同时有多个子协程工作,并且某个子协程或者主协程出现错误后,希望其它协程也停止工作。就可以用到 WithCancel
举个例子,例子并不是很好,但是可以理解怎么使用,假如同时有多个协程不停消费一个topic中的信息,另外有一个协程一直在监控mq的状态,一旦发现mq不可用,停止所有消费者消费。
package main
import (
"context"
"errors"
"fmt"
"time"
)
func main() {
// 自定义错误消息,不使用默认错误信息
context.Canceled = errors.New("mq 故障")
ctx, cancel := context.WithCancel(context.Background())
for i := 0; i < 5; i++ {
go func(i int) {
for {
select {
case <-ctx.Done():
fmt.Printf("%s:%s-%d停止消费\n", ctx.Err(), "consumer", i)
return
default:
fmt.Println("消费topic:order中的消息")
time.Sleep(time.Second)
}
}
}(i)
}
// 假设是一个监听mq的协程,如果发现故障就不让其他消费者消费了
go func() {
time.Sleep(time.Second * 2)
// 故障,通知消费者不在消费
cancel()
}()
// 暂时别让程序停止
time.Sleep(time.Second * 5)
}
执行结果
消费topic:order中的消息
消费topic:order中的消息
消费topic:order中的消息
消费topic:order中的消息
消费topic:order中的消息
消费topic:order中的消息
消费topic:order中的消息
消费topic:order中的消息
消费topic:order中的消息
消费topic:order中的消息
mq 故障:consumer-3停止消费
mq 故障:consumer-4停止消费
mq 故障:consumer-1停止消费
mq 故障:consumer-0停止消费
mq 故障:consumer-2停止消费
Process finished with the exit code 0
WithTimeout WithDeadline
源码中 WithTimeout 就是调用的 WithDeadline,所以我们介绍 WithTimeout 就可以了
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
从方法名可以看出,带有超时时间的上下文。超时处理在很多框架里都是必备的。所以去看很多go的源码基本上就都用到了context,比如gin框架,go中最基本的http库也是使用到了它。所以大家在项目中还是可以放心使用context
我们就以http标准库举个例子。以客户端携带超时时间为例。启动一个http服务,假设处理时间需要2s,那么我们发送两次请求,一次要求1s内返回数据,一次要求3s内返回数据,观察结果
package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"time"
)
func main() {
// 启动一个http服务
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Println("hello")
// 设置处理时间2s
time.Sleep(time.Second * 2)
})
go http.ListenAndServe(":8111", nil)
// 第一个请求携带超时时间1s
go func() {
ctx, _ := context.WithTimeout(context.Background(), time.Second)
request, _ := http.NewRequestWithContext(ctx, http.MethodGet, "http://127.0.0.1:8111", nil)
client := &http.Client{}
_, err := client.Do(request)
if err != nil {
fmt.Println("请求1 fail", err.Error())
return
}
fmt.Println("请求1 success")
}()
// 第二个请求携带超时时间3s
go func() {
ctx, _ := context.WithTimeout(context.Background(), time.Second*3)
request, _ := http.NewRequestWithContext(ctx, http.MethodGet, "http://127.0.0.1:8111", nil)
client := &http.Client{}
_, err := client.Do(request)
if err != nil {
fmt.Println("请求2 fail", err.Error())
return
}
fmt.Println("请求2 success")
}()
// 不让进程退出
ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt)
<-ch
}
执行结果
hello
hello
请求1 fail Get "http://127.0.0.1:8111": context deadline exceeded
请求2 success
到这大家应该清楚go中的标准库context简单使用了,大家也可以根据它提供的Context接口去实现自己满足自己需求的context,一些框架里面都是会自己实现的。后面我们会深入再讲解context
欢迎关注,学习不迷路!