go context详解
Context通常被称为上下文,在go中,理解为goroutine的运行状态、现场,存在上下层goroutine context的传递,上层goroutine会把context传递给下层goroutine。
每个goroutine在运行前,都要事先知道程序当前的执行状态,通常将这些状态封装在一个 context变量,传递给要执行的goroutine中。
在网络编程中,当接收到一个网络请求的request,处理request时,可能会在多个goroutine中处理。而这些goroutine可能需要共享Request的一些信息;当request被取消或者超时时,所有从这个request创建的goroutine也要被结束。
go context包不仅实现了在程序单元之间共享状态变量的方法,同时能通过简单的方法,在被调用程序单元外部,通过设置ctx变量的值,将过期或撤销等信号传递给被调用的程序单元。在网络编程中,如果存在A调用B的API,B调用C的 API,如果A调用B取消,那么B调用C也应该被取消,通过在A、B、C调用之间传递context,以及判断其状态,就能解决此问题。
通过context包,可以非常方便地在请求goroutine之间传递请求数据、取消信号和超时信息。
context包的核心时Context接口
// A Context carries a deadline, a cancellation signal, and other values across
// API boundaries.
//
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface {
// Deadline returns the time when work done on behalf of this context
// should be canceled. Deadline returns ok==false when no deadline is
// set. Successive calls to Deadline return the same results.
// 返回一个超时时间,到期则取消context。在代码中,可以通过 deadline 为io操作设置超时时间
Deadline() (deadline time.Time, ok bool) // Done returns a channel that's closed when work done on behalf of this
// context should be canceled. Done may return nil if this context can
// never be canceled. Successive calls to Done return the same value.
// The close of the Done channel may happen asynchronously,
// after the cancel function returns.
//
// WithCancel arranges for Done to be closed when cancel is called;
// WithDeadline arranges for Done to be closed when the deadline
// expires; WithTimeout arranges for Done to be closed when the timeout
// elapses.
//
// Done is provided for use in select statements:
//
// // Stream generates values with DoSomething and sends them to out
// // until DoSomething returns an error or ctx.Done is closed.
// func Stream(ctx context.Context, out chan<- Value) error {
// for {
// v, err := DoSomething(ctx)
// if err != nil {
// return err
// }
// select {
// case <-ctx.Done():
// return ctx.Err()
// case out <- v:
// }
// }
// }
//
// See https://blog.golang.org/pipelines for more examples of how to use
// a Done channel for cancellation.
// 返回一个channel, 用于接收context的取消或者deadline信号。当channel关闭,监听done信号的函数会立即放弃当前正在执行的操作并返回。如果 context实例是不可取消的,那么
// 返回 nil, 比如空 context, valueCtx
Done() <-chan struct{} // If Done is not yet closed, Err returns nil.
// If Done is closed, Err returns a non-nil error explaining why:
// Canceled if the context was canceled
// or DeadlineExceeded if the context's deadline passed.
// After Err returns a non-nil error, successive calls to Err return the same error.
// 返回一个error变量,从其中可以知道为什么context会被取消。
Err() error // Value returns the value associated with this context for key, or nil
// if no value is associated with key. Successive calls to Value with
// the same key returns the same result.
//
// Use context values only for request-scoped data that transits
// processes and API boundaries, not for passing optional parameters to
// functions.
//
// A key identifies a specific value in a Context. Functions that wish
// to store values in Context typically allocate a key in a global
// variable then use that key as the argument to context.WithValue and
// Context.Value. A key can be any type that supports equality;
// packages should define keys as an unexported type to avoid
// collisions.
//
// Packages that define a Context key should provide type-safe accessors
// for the values stored using that key:
//
// // Package user defines a User type that's stored in Contexts.
// package user
//
// import "context"
//
// // User is the type of value stored in the Contexts.
// type User struct {...}
//
// // key is an unexported type for keys defined in this package.
// // This prevents collisions with keys defined in other packages.
// type key int
//
// // userKey is the key for user.User values in Contexts. It is
// // unexported; clients use user.NewContext and user.FromContext
// // instead of using this key directly.
// var userKey key
//
// // NewContext returns a new Context that carries value u.
// func NewContext(ctx context.Context, u *User) context.Context {
// return context.WithValue(ctx, userKey, u)
// }
//
// // FromContext returns the User value stored in ctx, if any.
// func FromContext(ctx context.Context) (*User, bool) {
// u, ok := ctx.Value(userKey).(*User)
// return u, ok
// }
// 让context在goroutine之间共享数据,当然,这些数据需要时协程并发安全的。比如,共享了一个map,那么这个map的读写要加锁。
Value(key interface{}) interface{}
}
context的使用:
对于goroutine,他们的创建和调用关系总是像层层调用进行的,就像一个树状结构,而更靠顶部的context应该有办法主动关闭下属的goroutine的执行。为了实现这种关系,context也是一个树状结构,叶子节点总是由根节点衍生出来的。
要创建context树,第一步应该得到根节点,context.Backupgroup函数的返回值就是根节点。
// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context {
return background
}
该函数返回空的context,该context一般由接收请求的第一个goroutine创建,是与进入请求对应的context根节点,他不能被取消,也没有值,也没有过期时间。他常常作为处理request的顶层的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 interface{}) Context {}
函数都接收一个Context类型的parent,并返回一个context类型的值,这样就层层创建除不同的context,子节点是从复制父节点得到,并且根据接收参数设定子节点的一些状态值,接着就可以将子节点传递给下层的goroutine了。
怎么通过context传递改变后的状态呢?
在父goroutine中可以通过Withxx方法获取一个cancel方法,从而获得了操作子context的权力。
WithCancel函数,是将父节点复制到子节点,并且返回一个额外的CancelFunc函数类型变量,该函数类型的定义为:type CancelFunc func()
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done chan struct{} // created lazily, closed by first cancel call
children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
// 懒汉式创建,只有在调用Done()方法时,才会创建;该函数返回的是一个只读的 chan,没有地方向这个chan中写数据, 直接读取协程会被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
}
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
c.mu.Lock()
// 已经被其他协程取消
if c.err != nil {
c.mu.Unlock()
return // already canceled
}
c.err = err
// 关闭channel,通知其他协程
if c.done == nil {
c.done = closedchan
} else {
close(c.done)
}
// 遍历他的所有子节点 children,
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.
// 递归的取消所有子 ctx
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
if removeFromParent {
// 从父ctx中移除自己
removeChild(c.Context, c)
}
}
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
// 传播取消行为,根据 parent的情况,进行cancel还是将c添加的parent的children中
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
// 父context是否是可取消的context
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
done := parent.Done()
if done == closedchan || done == nil {
return nil, false
}
// 如果是 cancelCtx 或者 timerCtx, 则返回 parent,true; 否则返回 nil, false
p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
if !ok {
return nil, false
}
p.mu.Lock()
ok = p.done == done
p.mu.Unlock()
//
if !ok {
return nil, false
}
return p, true
}}
// propagateCancel arranges for child to be canceled when parent is.
// 把child ctx cancel()关联到 parent节点上
func propagateCancel(parent Context, child canceler) {
done := parent.Done()
// 如果父类 context 不可取消,直接return
if done == nil {
return // parent is never canceled
}
select {
case <-done:
// parent is already canceled
// 父类context已经canceled, child直接cancel()
child.cancel(false, parent.Err())
return
default:
}
// parent是否是可取消的cancelContext, 如果是,则挂靠上去
if p, ok := parentCancelCtx(parent); ok {
// 如果有
p.mu.Lock()
// err != nil,说明挂靠的parent已经被关闭,child直接cancel()
if p.err != nil {
// parent has already been canceled
child.cancel(false, p.err)
} else {
// err == nil ,挂靠的parent没有被关闭 ;将child放入挂靠的parent的children数组中
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
// 走到这里,说明树上没有cancelCtx
atomic.AddInt32(&goroutines, +1)
// 新起一个goruntine
go func() {
select {
case <-parent.Done():
// 如果收到取消信号,child cancel
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
调用 CancelFunc 将撤销对应的子context对象。在父goroutine中,通过 WithCancel 可以创建子节点的 Context, 还获得了子goroutine的控制权,一旦执行了 CancelFunc函数,子节点Context就结束了,子节点需要如下代码来判断是否已经结束,并退出goroutine:
select {
case <- ctx.Done():
fmt.Println("do some clean work ...... ")
}
WithDeadline函数作用和WithCancel差不多,也是将父节点复制到子节点,但是其过期时间是由deadline和parent的过期时间共同决定。当parent的过期时间早于deadline时,返回的过期时间与parent的过期时间相同。父节点过期时,所有的子孙节点必须同时关闭。
WithTimeout函数和WithDeadline类似,只不过,他传入的是从现在开始Context剩余的生命时长。他们都同样也都返回了所创建的子Context的控制权,一个CancelFunc类型的函数变量。
当顶层的Request请求函数结束时,我们可以cancel掉某个context,而子孙的goroutine根据select ctx.Done()来判断结束。
// 使用WithDeadline 和 WithTimeout 都会生成一个 timerCtx, WithTimeout就是用 WithDeadline实现的。
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
// 如果父节点的deadline更靠前,那么该 d就可以丢弃,使用父节点的 deadline
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
// 把当前 c节点ctx cancel函数关联到 parent节点上
propagateCancel(parent, c)
// 获取到 d的时间
dur := time.Until(d)
if dur <= 0 {
// 已经超时了,退出
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func() { c.cancel(false, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
// parent节点到现在还没有取消
if c.err == nil {
// 到时间,自动退出
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
WithValue函数,返回parent的一个副本,调用该副本的Value(key) 方法将得到value。这样,我们不仅将根节点原有的值保留了, 还在子孙节点中加入了新的值;注意如果存在key相同,则会覆盖。
func WithValue(parent Context, key, val interface{}) Context {
// key必须为非空,且可比较
if key == nil {
panic("nil key")
}
if !reflectlite.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
// 这里使用递归,c.Context就是 c.Parent
return c.Context.Value(key)
}
小结:
1. context包通过构建树形关系的context,来达到上一层goroutine对下一层goroutine的控制。对于处理一个request请求操作,需要通过goroutine来层层控制goroutine,以及传递一些变量来共享。
2. context变量的请求周期一般为一个请求的处理周期。即针对一个请求创建context对象;在请求处理结束后,撤销此ctx变量,释放资源。
3. 每创建一个goroutine,要不将原有context传递给子goroutine,要么创建一个子context传递给goroutine.
4. Context能灵活地存储不同类型、不同数目的值,并且使多个Goroutine安全地读写其中的值。
5. 当通过父 Context对象创建子Context时,可以同时获得子Context的撤销函数,这样父goroutine就获得了子goroutine的撤销权。
原则:
1. 不要把context放到一个结构体中,应该作为第一个参数显式地传入函数
2. 即使方法允许,也不要传入一个nil的context,如果不确定需要什么context的时候,传入一个context.TODO
3. 使用context的Value相关方法应该传递和请求相关的元数据,不要用它来传递一些可选参数
4. 同样的context可以传递到多个goroutine中,Context在多个goroutine中是安全的
5. 在子context传入goroutine中后,应该在子goroutine中对该子context的Done channel进行监控,一旦该channel被关闭,应立即终止对当前请求的处理,并释放资源。
go context详解的更多相关文章
- Android中Context详解 ---- 你所不知道的Context
转自:http://blog.csdn.net/qinjuning/article/details/7310620Android中Context详解 ---- 你所不知道的Context 大家好, ...
- Android中Context详解 ---- 你所不知道的Context(转)
Android中Context详解 ---- 你所不知道的Context(转) 本文出处 :http://b ...
- Android中Context详解
大家好, 今天给大家介绍下我们在应用开发中最熟悉而陌生的朋友-----Context类 ,说它熟悉,是应为我们在开发中时刻的在与它打交道,例如:Service.BroadcastReceiver.A ...
- Android中的Context详解
前言:本文是我读<Android内核剖析>第7章 后形成的读书笔记 ,在此向欲了解Android框架的书籍推荐此书. 大家好, 今天给大家介绍下我们在应用开发中最熟悉而陌生的朋友---- ...
- 转:Android中Context详解 ---- 你所不知道的Context
转:http://blog.csdn.net/qinjuning/article/details/7310620 转:http://blog.csdn.net/lmj623565791/article ...
- Context详解
前言 Context在android中的作用不言而喻,当我们访问当前应用的资源,启动一个新的activity的时候都需要提供Context,而这个Context到底是什么呢,这个问题好像很好回答又好像 ...
- Android开发 Context详解与类型 转载
转载地址:https://blog.csdn.net/guolin_blog/article/details/47028975 个人总结: Context分为 activity : activity其 ...
- Golang并发模型之Context详解
对于 Golang 开发者来说context(上下文)包一定不会陌生.但很多时候,我们懒惰的只是见过它,或能起到什么作用,并不会去深究它. 应用场景:在 Go http 包的 Server 中,每一个 ...
- golang语言中的context详解,Go Concurrency Patterns: Context
https://blog.golang.org/context Introduction In Go servers, each incoming request is handled in its ...
- Android面试收集录18 Android Context详解
Activity mActivity =new Activity() 作为Android开发者,不知道你有没有思考过这个问题,Activity可以new吗?Android的应用程序开发采用JAVA语言 ...
随机推荐
- 除了增删改查你对MySQL还了解多少?
目录 除了增删改查你对MySQL还了解多少? MySQL授权远程连接 创建用户.授权 客户端与服务器连接的过程 TCP/IP 命名管道和共享内存 Unix域套接字文件 查询优化 MySQL中走与不走索 ...
- phpmyadmin 4.8.1 文件包含漏
一. 启动环境 1.双击运行桌面phpstudy.exe软件 2.点击启动按钮,启动服务器环境 二.代码审计 1.双击启动桌面Seay源代码审计系统软件 3.点击新建项目按钮,弹出对画框中选择(C:\ ...
- [转载]nc命令详解
最近在搞反向连接,试来试去发现最好的工具还是nc.正好趁这个机会把nc的用法总结一下: 1.端口扫描: nc -vv ip port 例:nc -vv 192.168.1.1 5000 扫描192.1 ...
- CF914G Sum the Fibonacci(FWT,FST)
CF914G Sum the Fibonacci(FWT,FST) Luogu 题解时间 一堆FWT和FST缝合而来的丑陋产物. 对 $ cnt[s_{a}] $ 和 $ cnt[s_{b}] $ 求 ...
- automake的使用2
前言 如果你的入口文件main.c和依赖的文件不是在同一个目录中的,使用Autotools来管理项目的时候会稍微复杂一下. 在不同的目录下,项目会生成*.a文件的静态连接(静态连接相当于将多个.o目标 ...
- 从字符串某位置开始的递增串(dfs)注意for循环中下标的错误
#include <iostream> #include <string> using namespace std; char res[50];int tag=1; void ...
- 为MySQL加锁?
在日常操作中,UPDATE.INSERT.DELETE InnoDB会自动给涉及的数据集加排他锁,一般的 SELECT 一般是不加任何锁的.我们可以使用以下方式显示的为 SELECT 加锁. 共享锁: ...
- super.getClass()方法调用?
下面程序的输出结果是多少? import java.util.Date; public class Test extends Date{ public static void main(String[ ...
- SpringMvc用什么对象从后台向前台传递数据的?
通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前台就可以通过el表达式拿到.
- Kafka 判断一个节点是否还活着有那两个条件?
(1)节点必须可以维护和 ZooKeeper 的连接,Zookeeper 通过心跳机制检查每 个节点的连接 (2)如果节点是个 follower,他必须能及时的同步 leader 的写操作,延时不能太 ...