声明:个人笔记,部分内容摘抄自鸟窝老师.具体细节可以到极客时间上购买鸟窝老师的 <Go并发编程实战>
context 是什么?
故名思义: context翻译过来就是上下文的意思, 在我们的开发场景中, 上下文也是不可或缺的,缺少了它,我们就不能获取完整的程序信息。
例如:服务端收到客户端的HTTP请求之后,可以把客户端的IP地址,端口,token,时间等信息放入到上下文当中,这个上下文可以在后端的方法调用中传递。
context的来历?
Go 在 1.7 的版本中才正式把 Context 加入到标准库中。在这之前,很多 Web 框架在定义自己的 handler 时,都会传递一个自定义的 Context,把客户端的信息和客户端的请求信息放入到 Context 中。Go 最初提供了 golang.org/x/net/context 库用来提供上下文信息,最终还是在 Go1.7 中把此库提升到标准库 context 包中。
context 在go语言中的用途?
- 可以上下文信息传递,比如:处理HTTP请求/在请求处理链路上传递信息
- 控制goroutine的生命周期
3 .超时控制的方法调用 - 可以取消的方法调用
context的基本方法.
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
context Deadline()基本使用
- Deadline()这个方法会返回这个context被取消的截止日期.如果没有设置截至日期,ok的值就是false.
可以看下下面的这人例子:
package main
import (
"context"
"fmt"
"time"
)
func watch1(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Println(name, "收到信号,监控退出,time=", time.Now().Unix())
return
default:
fmt.Println(name, "gouroutine监控中,time=", time.Now().Unix())
time.Sleep(1 * time.Second)
}
}
}
func main() {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second*3))
go watch1(ctx, "监控1")
go watch1(ctx, "监控2")
fmt.Println("现在开始等待5秒,time=", time.Now().Unix())
time.Sleep(5 * time.Second)
fmt.Println("等待5秒结束,准备调用cancel()函数,发现两个子协程已经结束了,time=", time.Now().Unix())
cancel()
}
WithDeadline 会返回一个 parent 的副本,并且设置了一个不晚于参数 d 的截止时间,类型为 timerCtx(或者是 cancelCtx)。
如果当前时间已经超过了截止时间,就直接返回一个已经被 cancel 的 timerCtx。否则就会启动一个定时器,到截止时间取消这个 timerCtx。
context Deadline()的源码实现
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
// 如果parent的截止时间更早,直接返回一个cancelCtx即可
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
propagateCancel(parent, c)
dur := time.Until(d)
if dur <= 0 { //当前时间已经超过了截止时间,直接cancel
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func() { c.cancel(false, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
// 设置一个定时器,到截止时间后取消
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
Done方法
Done() <-chan struct{}
Done方法返回一个channel对象。在congtext被取消时,此channel会被close关闭。如果没有被取消,可能会返回nil,后续的Done调用总是返回相同的结果,当done被close的时候,可以通过ctx.Err 获取错误信息。
如果Done没有被close,err方法为nil
如果Done被close,Err方法会返回Done被close的原因。
context包提供了Done()方法, 他返回一个 <-chan struct{} 对象。
Go语言context标准库的Context类型提供了一个Done()方法,该方法返回一个类型为 <-chan struct{}的channel。每次context收到取消事件后这个channel都会接收到一个struct{}类型的值。所以在Go语言里监听取消事件就是等待接收<-ctx.Done()。
举例来说,假设一个HTTP服务器需要花费两秒钟来处理一个请求。如果在处理完成之前请求被取消,我们想让程序能立即中断不再继续执行下去:
func main() {
http.ListenAndServe(":8000", http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
ctx := request.Context()
select {
case <-time.After(2 * time.Second):
// 如果两秒以后收到了一个消息,意味相应已经处理完成
writer.Write([]byte("响应处理完成"))
case <-ctx.Done():
// 如果处理完成了, 记录被取消的信息
fmt.Fprint(os.Stderr, "request cancelled\n")
}
}))
}
context 中常用生成顶层context的方法
- context.BackGroup()
他会返回一个空的context,没有任何值,不会被cancel,不会超时,没有截止日期,一般用于在主函数,初始化,测试以及创建跟context的时候。
- context.TODO()
返回一个非nil,空的context,不会被cancel,不会超时,没有截止日期。当你不清楚是否该用 Context,或者目前还不知道要传递一些什么上下文信息的时候,就可以使用这个方法。
context中特殊用途的顶层方法
- WithValue
WithValue 基于 parent Context 生成一个新的 Context,保存了一个 key-value 键值对。它常常用来传递上下文。
WithValue保存了一个 key-value的键值对
func main() {
ctx := context.WithValue(context.TODO(), "key1", "001")
fmt.Println(ctx.Value("key1"))
}
- withCancel 方法
WithCancel 方法返回 parent 的副本,只是副本中的 Done Channel 是新建的对象,它的类型是 cancelCtx。
我们常常在一些需要主动取消长时间的任务时,创建这种类型的 Context,然后把这个 Context 传给长时间执行任务的 goroutine。当需要中止任务时,我们就可以 cancel 这个 Context,这样长时间执行任务的 goroutine,就可以通过检查这个 Context,知道 Context 已经被取消了,简单点来说:就是控制goroutine的生命周期!
例如下面的例子:
- WithTimeout
WithTimeout 其实是和 WithDeadline 一样,只不过一个参数是超时时间,一个参数是截止时间。超时时间加上当前时间,其实就是截止时间