Golang 中正确理解context
context(上下文)作用
golang中的context主要为了处理单个请求域与多个goroutine之间的数据共享,取消信号,截止时间等问题,在一个网络请求中,创建多个goroutine,就可以通过context去追踪所有的goroutine,
context结构
type Context interface { # conetext是否会被取消以取消的时间 Deadline() (deadline time.Time, ok bool) # context被取消或者到了deadline,返回一个关闭的channel Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
-
Deadline方法是获取设置的截止时间的意思,第一个返回式是截止时间,到了这个时间点,Context会自动发起取消请求;第二个返回值ok==false时表示没有设置截止时间,如果需要取消的话,需要调用取消函数进行取消。
-
Done方法返回一个只读的chan,类型为struct{},我们在goroutine中,如果该方法返回的chan可以读取,则意味着parent context已经发起了取消请求,我们通过Done方法收到这个信号后,就应该做清理操作,然后退出goroutine,释放资源。之后,Err 方法会返回一个错误,告知为什么 Context 被取消。
-
Err方法返回取消的错误原因,因为什么Context被取消。
-
Value方法获取该Context上绑定的值,是一个键值对,所以要通过一个Key才可以获取对应的值,这个值一般是线程安全的
canceler接口:
实现了下面定义的两个方法的 Context,就表明该 Context 是可取消的。
type canceler interface { cancel(removeFromParent bool, err error) Done() <-chan struct{} }
实现canceler接口的cancelCtx:
type cancelCtx struct { Context // 保护之后的字段 mu sync.Mutex done chan struct{} children map[canceler]struct{} err error }
Done方法具体实现:
c.done 是 “懒汉式” 创建,只有调用了 Done () 方法的时候才会被创建。函数返回的是一个只读的 channel,而且没有地方向这个 channel 里面写数据。所以,直接调用读这个 channel,协程会被 block 住。一般通过搭配 select 来使用。一旦关闭,就会立即读出零值。
func (c *cancelCtx) Done() <-chan struct{} { c.mu.Lock() if c.done == nil { c.done = make(chan struct{}) } d := c.done c.mu.Unlock() return d }
cancel方法具体实现:
cancel() 方法的功能就是关闭 channel:close(c.done),在select中就不再是一个空的channel了;递归地取消它的所有子节点;从父节点从删除自己。最终通过关闭 channel,将取消信号传递给了它的所有子节点。
func (c *cancelCtx) cancel(removeFromParent bool, err error) { // 必须要传 err if err == nil { panic("context: internal error: missing cancel error") } c.mu.Lock() if c.err != nil { c.mu.Unlock() return // 已经被其他协程取消 } // 给 err 字段赋值 c.err = err // 关闭 channel,通知其他协程 if c.done == nil { c.done = closedchan } else { close(c.done) } // 遍历它的所有子节点 for child := range c.children { // 递归地取消所有子节点 child.cancel(false, err) } // 将子节点置空 c.children = nil c.mu.Unlock() if removeFromParent { // 从父节点中移除自己 removeChild(c.Context, c) } }
context的常用继承方法:
最基本的方法是context.background()
#传递父context作为参数,返回子context和取消函数用来取消context func WithCancel(parent Context) (ctx Context, cancel CancelFunc) #多传截止时间参数,到这个时间会自动取消context,也可以用取消函数提前取消 func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) 同理,过了多少时间自动取消 func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) 生成绑定键值对的context,绑定数据可以通过context.Value访问,可以通过这种方式上下文传递数据 func WithValue(parent Context, key, val interface{}) Context
同一个context是可以拥有两种身份的:
既可以携带值也可以带有取消函数
var key string="name" func main() { ctx, cancel := context.WithCancel(context.Background()) //附加值 valueCtx:=context.WithValue(ctx,key,"【监控1】") go watch(valueCtx) time.Sleep(10 * time.Second) fmt.Println("可以了,通知监控停止") cancel() //为了检测监控过是否停止,如果没有监控输出,就表示停止了 time.Sleep(5 * time.Second) } func watch(ctx context.Context) { for { select { case <-ctx.Done(): //取出值 fmt.Println(ctx.Value(key),"监控退出,停止了...") return default: //取出值 fmt.Println(ctx.Value(key),"goroutine监控中...") time.Sleep(2 * time.Second) } } }
context的使用与总结:
- 不要把Context放在结构体中,要以参数的方式传递,parent Context一般为Background
- 应该要把Context作为第一个参数传递给入口请求和出口请求链路上的每一个函数,放在第一位,变量名建议都统一,如ctx。
- 给一个函数方法传递Context的时候,不要传递nil,否则在trace追踪的时候,就会断了连接
- Context的Value相关方法应该传递必须的数据,不要什么数据都使用这个传递
- Context是线程安全的,可以放心的在多个goroutine中传递
- 可以把一个 Context 对象传递给任意个数的 gorotuine,对它执行取消操作时,所有 goroutine 都会接收到取消信号。
本文来自博客园,作者:LeeJuly,转载请注明原文链接:https://www.cnblogs.com/peterleee/p/13472341.html