go context专题(三)- context 工作机制和代码分析
2017-08-29 20:58
411 查看
talk is cheap show me the code
运行结果
![](https://img-blog.csdn.net/20170831203327243?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGl0dGF0YQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
程序分析:
使用context封装的过程中,程序在底层实际上维护了两条关系链,理解这个关系链才是真的懂context包:
两条引用关系链分别是:
一条是 children key构成的从根到叶子的Context实例的引用关系,这个关系在调用With函数时进行维护(调用func propagateCancel(parent Context, child canceler) 函数维护)
上面程序有一层这样的树形结构(本示例是个链):
ctxa.children --->ctxb
ctxb.children--->ctxc
这个树的作用:提供一种从根可以遍历到叶子的方法,context包的取消广播通知的核心就是基于这一点。
反正这条链是从树根像下层逐层广播下去,当然你也可以在任意一个子树上调用取消通知,一样会扩散到整棵树。
ctxa收到退出通知,会通知到其绑定goroutine work1,同时也会广播给ctxb和ctxc绑定的goroutines work2 work3
同理:ctxc 收到退出通知,会通知到绑定的goroutine work2,同时也会广播给ctxc绑定的goroutine work3
一条是在构造Context的对象中不断的包裹Context实例所形成的一个引用关系链,从外到内的引用
ctxc.Context -->oc
ctxc.Context.Context -->ctxb
ctxc.Context.Context.cancelCtx -->ctxa
ctxc.Context.Context.cancelCtx.Context -->new(emptyCtx) //context.Background()
这个关系什么用,很简单为了切断和上层的Context的实例之间的关系,比如ctxb自己取消了自己(定时器),没有必要在退出通知广播链上继续存在,就找到parent,然后执行 delete(parent.children,ctxb),就把自己从广播链条上释放掉。
看上面如果有困难,可以看下核心代码的解读
负责创建Context对象并且把Context对象传递给后端新起的goroutine
在请求期结束其要负责调用Context对象的cancel方法通知后端goroutine释放资源
后端接收通知goroutine
后端每一个接受Context的goroutine可以继续使用WithCancel, WithDeadline, WithTimeout, or WithValue 包装这个Context,然后传向后端
在处理正确逻辑的过程中,要注意监听是否收到退出通知,后端调用链里面的所有的goroutine要增加监听退出通知的逻辑
func Background() Context
unc TODO() Context
2.包装上一步创建的Context对象,使其具有特定的功能
//这个函数是context package的核心,几乎所有的封装都是从其开始,原因很简单:我们使用context的包的核心就是使用其退出通知广播功能,如果不从其开始构造,
说明我们的使用已经偏离的context包设计的初衷。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
3.每一层goroutine可以继续封装Context对象,封装自己需要的功能
4.最上层的goroutine负责在一个请求处理完后及时的调用cancel函数,通知后端的goroutine释放资源
5.后端的goroutine 要负责监听Context.Done() 返回chan,及时的退出并且释放资源
整个调用栈如下图
源码分析context的工作机理
下面我写的一段简单的实验性质的代码,简单的阐述一下context库的最基本的用法。package main import ( "context" "fmt" "time" ) /* Author: andes Date : 20170821 */ //define a new type include a Context Field type otherContext struct { context.Context } func main() { //Construct a *cancelCtx type object ctxa, cancel := context.WithCancel(context.Background()) go work(ctxa, "work1") //Construct a *timerCtx type object wrapped by *cancelCtx tm := time.Now().Add(3 * time.Second) ctxb, _ := context.WithDeadline(ctxa, tm) go work(ctxb, "work2") oc := otherContext{ctxb} //Construct a *cancelCtx type object wrapped by oc ctxc := context.WithValue(oc, "key", "god andes,pass from main ") go workWithValue(ctxc, "work3") time.Sleep(10 * time.Second) cancel() time.Sleep(5 * time.Second) fmt.Println("main stop") } //do something func work(ctx context.Context, name string) { for { select { case <-ctx.Done(): fmt.Printf("%s get msg to cancel\n", name) return default: fmt.Printf("%s is running \n", name) time.Sleep(1 * time.Second) } } } //do something and pass values by context func workWithValue(ctx context.Context, name string) { for { select { case <-ctx.Done(): fmt.Printf("%s get msg to cancel\n", name) return default: value := ctx.Value("key").(string) fmt.Printf("%s is running value=%s \n", name, value) time.Sleep(1 * time.Second) } } }
运行结果
程序分析:
使用context封装的过程中,程序在底层实际上维护了两条关系链,理解这个关系链才是真的懂context包:
两条引用关系链分别是:
一条是 children key构成的从根到叶子的Context实例的引用关系,这个关系在调用With函数时进行维护(调用func propagateCancel(parent Context, child canceler) 函数维护)
上面程序有一层这样的树形结构(本示例是个链):
ctxa.children --->ctxb
ctxb.children--->ctxc
这个树的作用:提供一种从根可以遍历到叶子的方法,context包的取消广播通知的核心就是基于这一点。
反正这条链是从树根像下层逐层广播下去,当然你也可以在任意一个子树上调用取消通知,一样会扩散到整棵树。
ctxa收到退出通知,会通知到其绑定goroutine work1,同时也会广播给ctxb和ctxc绑定的goroutines work2 work3
同理:ctxc 收到退出通知,会通知到绑定的goroutine work2,同时也会广播给ctxc绑定的goroutine work3
一条是在构造Context的对象中不断的包裹Context实例所形成的一个引用关系链,从外到内的引用
ctxc.Context -->oc
ctxc.Context.Context -->ctxb
ctxc.Context.Context.cancelCtx -->ctxa
ctxc.Context.Context.cancelCtx.Context -->new(emptyCtx) //context.Background()
这个关系什么用,很简单为了切断和上层的Context的实例之间的关系,比如ctxb自己取消了自己(定时器),没有必要在退出通知广播链上继续存在,就找到parent,然后执行 delete(parent.children,ctxb),就把自己从广播链条上释放掉。
看上面如果有困难,可以看下核心代码的解读
ctxa, cancel := context.WithCancel(context.Background()) /* ctxa内部状态---->ctxa=&cancelCtx { Context: new(emptyCtx) } */ go work(ctxa, "work1") tm := time.Now().Add(3 * time.Second) ctxb, _ := context.WithDeadline(ctxa, tm) /* ctxb内部状态---> ctxb=&timerCtx{ cancelCtx: ctxa dataline:tm } 同时触发ctxa的children维护到ctxb的关系 */ go work(ctxb, "work2") oc := otherContext{ctxb} ctxc := context.WithValue(oc, "key", "pass from main ") /* ctxc--->ctxc=&cancelCtx { Context: oc } 同时通过oc.Context找到ctxb,通过ctxb.cancelCtx找到ctxa,在ctxa的children字段中维护ctxc */ go workWithValue(ctxc, "work3")
context 使用过程中参与角色
通知发起goroutine负责创建Context对象并且把Context对象传递给后端新起的goroutine
在请求期结束其要负责调用Context对象的cancel方法通知后端goroutine释放资源
后端接收通知goroutine
后端每一个接受Context的goroutine可以继续使用WithCancel, WithDeadline, WithTimeout, or WithValue 包装这个Context,然后传向后端
在处理正确逻辑的过程中,要注意监听是否收到退出通知,后端调用链里面的所有的goroutine要增加监听退出通知的逻辑
context 使用通用模式
1.创建一个原始的Context实例对象func Background() Context
unc TODO() Context
2.包装上一步创建的Context对象,使其具有特定的功能
//这个函数是context package的核心,几乎所有的封装都是从其开始,原因很简单:我们使用context的包的核心就是使用其退出通知广播功能,如果不从其开始构造,
说明我们的使用已经偏离的context包设计的初衷。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
3.每一层goroutine可以继续封装Context对象,封装自己需要的功能
4.最上层的goroutine负责在一个请求处理完后及时的调用cancel函数,通知后端的goroutine释放资源
5.后端的goroutine 要负责监听Context.Done() 返回chan,及时的退出并且释放资源
整个调用栈如下图
相关文章推荐
- 通过调用C语言的库函数与在C代码中使用内联汇编两种方式来使用同一个系统调用来分析系统调用的工作机制
- 代码分析-Boost库Bind之工作机制
- Spring Cloud 请求重试机制核心代码分析
- 《Linux内核分析》之图解汇编代码以分析计算机是如何工作的
- 深入分析 Java I/O 的工作机制(一)
- 深入分析Java I/O 工作机制
- SQLite 锁机制学习总结 锁状态转换及锁机制实现代码分析
- hostapd源代码分析(二):hostapd的工作机制
- 第二章 Struts2的工作机制及分析
- 基于.NET 2.0的GIS开源项目SharpMap分析手记(十四):ASP.NET2.0实现无刷新客户端回调的Callback机制及例子代码下载
- 深入分析 Java I/O 的工作机制
- 深入分析 Java I/O 的工作机制
- 深入分析java web 的ClassLoader工作机制(一)
- RequestContextHolder分析及Java代码
- Go 语言机制之逃逸分析
- Linux -- 内存控制之oom killer机制及代码分析
- Linux内核分析之系统调用工作机制简析
- 深入分析 Java I/O 的工作机制
- Raid1源代码分析--Barrier机制
- Linux内核分析第四周学习总结——系统调用的工作机制