Contex包了解一下
Context包了解一下
为啥需要Context
在并发编程中,由于超时、取消操作或者一些异常情况,往往需要进行抢占操作或者中断后续操作,context营运而生,channel也能用但是层级太深了的话channel就不好用了。
Context接口
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Context接口有四个函数
- Deadline: 返回绑定当前context的任务被取消的时间,如果没有设置时间,就返回ok == false
- Done:在context的任务被取消时,返回一个关闭的channel,如果contex不能被取消,就返回nil
- Err:如果Done返回的channel没有被关闭,返回nil,如果关闭了,就返回错误表示任务结束的原因,如果是context被取消,就返回Canneled,如果是超时,返回DeadLineExceeded
- Value:返回context存储的键值对中的key对应的值,如果没有就返回nil。
创建Context
创建空的Context
ctx := context.Background()
ctx := context.TODO()
这两个函数都返回一个空的context,他们不能被取消,没有deadline,没有key-value.
派生Context
空context肯定不能满足我们的需求,我们需要根据需求获得好用的context
下面演示如果获取一个可以被取消的context
ctx ,cancel := context.WithCancel(ctx) //ctx是一个空的context
contex.WithCancel函数接收一个context作为父context,派生出一个可以cancel的子context,然后返回子context和一个函数,调用这个函数就会取消context。
下面两个函数也用来派生context
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) //返回带有timeout的context,到时间自动cancel或者你手动cancel
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) //返回带有deadline的context,需要设置一个具体时间。
func WithValue(parent Context, key, val interface{}) Context //返回带有key-value的context
举例
在本地,函数之间
这个例子是这样的,我么用一个函数去做一件事,他要一些时间去执行(这里定位5s),然后我们起一个协程,2s后执行cancel(),就是将context关掉,限制任务执行时间。我们通过select监听context.Done()去看有没有被cancel,用time.After去完成5s这个计时。
当2s到的时候,cancel执行,然后ctx.Done()就有一个关闭的通道返回了
func doSomething(ctx context.Context) {
select {
case <-time.After(5 * time.Second): //5秒后开始干活
fmt.Println("finish do something")
case <-ctx.Done(): //监听context有没有被cancel,被cancel后ctx.Done()就会返回一个通道
err := ctx.Err()
if err != nil {
fmt.Println(err.Error())
}
}
}
func main() {
ctx := context.Background() //返回一个空的context
ctx, cancel := context.WithCancel(ctx)
go func() { //2秒后将context给cancel掉
time.Sleep(2 * time.Second)
cancel()
}()
doSomething(ctx)
}
最后运行的效果就是:
客户端与服务器之间
我们写一个server,接收http请求,然后再写一个client,client向server发送请求,带一个context过去,这个context有一个timeout
client:向server发送请求,带一个timeout为3秒的context过去。
func main(){
ctx := context.Background()
ctx ,cancel := context.WithTimeout(ctx,3 * time.Second)
defer cancel()
rep,err := http.NewRequest(http.MethodGet,"http://localhost:8080",nil)
if err != nil {
log.Fatalln(err.Error())
}
rep = rep.WithContext(ctx)
resp,err := http.DefaultClient.Do(rep)
if err != nil {
log.Fatal(err.Error())
}
defer resp.Body.Close()
respBytes,err := io.ReadAll(resp.Body)
if err != nil {
log.Panicln(err.Error())
}
fmt.Println(string(respBytes))
}
server:server接收请求中的context进行处理。server回5秒再返回helloworld但是timeout设置的是3秒,所以server这里会收到ctx.Done()的信号,然后就会打印一些东西。
func handler(w http.ResponseWriter,r *http.Request) {
ctx := r.Context()
select {
case <-time.After(5*time.Second):
_,err := fmt.Fprintln(w,"hello world")
if err != nil {
log.Panicln(err.Error())
}
case <-ctx.Done():
err := ctx.Err()
if err != nil {
fmt.Println(err.Error())
}
}
log.Println("handler done")
}
func main() {
http.HandleFunc("/",handler)
http.ListenAndServe(":8080",nil)
}
client执行效果:
server执行效果:
这样的话我们可以在超时之后return掉,不浪费资源。