context包是在go1.7版本中从golang.org/x/net/context包挪入到标准库中。从标准库的GOROOT/src/context包下我们研究分析源码可以看到其底层实现是基于一个Context interface及其实现类(strcut)来实现:
通过以上的context功能特点context可以运用于多个goroutine之间的进程管理,信息通知,和数据传递的程序中使用。
context包基于一个Context接口抽象出4种行为方法,其子类实现具体的方法即可实现各自的功能特点
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Deadline(): 截止日期,该方法若没设置deadline 一个具体时间,则ok为false
Done() : 用于生成一个只读的channel
Err() : 当该上述channel关闭时给定一个err错误的理由提示,没关闭err为nil
Value() : 通过key获取 value的一个方法
canceler接口里定义了一个cancel()方法用于关闭channel管道和从父context children map移除子context。Done()方法用于返回一个只读的channel.
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}
实现了该接口的context上下文可以直接进行取消动作。
type emptyCtx int
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (*emptyCtx) Done() <-chan struct{} {
return nil
}
func (*emptyCtx) Err() error {
return nil
}
func (*emptyCtx) Value(key interface{}) interface{} {
return nil
}
func (e *emptyCtx) String() string {
switch e {
case background:
return "context.Background"
case todo:
return "context.TODO"
}
return "unknown empty Context"
}
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
可以看到上述emptyCtx是一个自定义类型,对Context里的方法进行了一个空实现,目的为下文的background和toto对象做一个实列,提供一个顶层上下文(top-level context),即祖先context
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
此处通过内建new()方法实列两个指向emptyCtx的指针对象,通过Background()和TODO()方法向外提供其引用。background一般常用做顶层context,初始化,测试。toto一般当不清楚要使用哪个上下文或者它还不可用时使用。
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
}
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) Err() error {
c.mu.Lock()
err := c.err
c.mu.Unlock()
return err
}
/**
此方法用于实例一个带取消的cancelCtx,返回cancelCtx的实例和一个取消函数
*/
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}
// 判断父类是否已被取消,若取消(err!=nil)则其子context调用取消,否则将其加入到父context children map里
func propagateCancel(parent Context, child canceler) {
if parent.Done() == nil {
return // parent is never canceled
}
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
if p.err != nil {
// parent has already been canceled
child.cancel(false, p.err)
} else {
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
//不断需找其最近的一个父context是否是cancelCtx类型,是返回则true 否则 false
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
for {
switch c := parent.(type) {
case *cancelCtx:
return c, true
case *timerCtx:
return &c.cancelCtx, true
case *valueCtx:
parent = c.Context
default:
return nil, false
}
}
}
//从父context children map里移除子context
func removeChild(parent Context, child canceler) {
p, ok := parentCancelCtx(parent)
if !ok {
return
}
p.mu.Lock()
if p.children != nil {
delete(p.children, child)
}
p.mu.Unlock()
}
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 该context早已取消
}
c.err = err
if c.done == nil {
c.done = closedchan
} else {
close(c.done) //关闭channel通道,此时对<-ctx.Done()则执行通过(context原理)
}
//遍历父 children里的context,对应一个一个的cancel
for child := range c.children {
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
if removeFromParent {
removeChild(c.Context, c) //从父children map移除自己
}
}
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu. 此对象受其父成员锁的保护
deadline time.Time
}
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
return c.deadline, true
}
func (c *timerCtx) String() string {
return contextName(c.cancelCtx.Context) + ".WithDeadline(" +
c.deadline.String() + " [" +
time.Until(c.deadline).String() + "])"
}
timerCtx 继承里cancelCtx接口拥有其方法行为,且追加一个deadline成员变量用于标记该context到达该时间后自动取消context(ps:close channel, <-channel pass)
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
//若其父context设置了到期截止日期,且其父context的截止日期早已本context的截止日期,则直接WithCancel()取消
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
//init timerCtx
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
//判断父context是否取消:
//若取消则取消本context
//若没取消则将本context加入到父context的children map里管理
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 {
//timerCtx灵魂所在:使用AfterFunc()方法实现到期后自动执行cancel()方法进行取消
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) } //返回实例的本contxet,和func可直接调用此func立即取消本contet
}
通过对WithDeadline()方法的仔细分析可以看到timerCtx是通过time.AfterFunc()函数实现对context的到期自动调用func(){c.cancel(bool,error)}方法实现取消功能。
func (c *timerCtx) cancel(removeFromParent bool, err error) {
c.cancelCtx.cancel(false, err) //调用父cancel()方法实现真正的取消
if removeFromParent {
// Remove this timerCtx from its parent cancelCtx's children.
removeChild(c.cancelCtx.Context, c) //从父context移除本context
}
c.mu.Lock()
if c.timer != nil {
c.timer.Stop() //stop timer计时器
c.timer = nil
}
c.mu.Unlock()
}
cancel()方法对其父cancel()方法进行重写来实现对相关资源的释放,但内部还是通过其父cancel()来实现取消功能
type valueCtx struct {
Context
key, val interface{}
}
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}
分析可得,valueCtx 采用key-value的形式来存储数据。基础了Context接口,重写了Value()方法。若设置的key在本context找不到则从其父级获取,逐级推进。实现了context链之间的数据传递和共享。此上下文没有取消的功能。故一般常和其他context组合使用。如cancelCtx,timerCtx.
cancelCtx,cancelFunc := context.WithCancel(context.Background())
cancelFunc () 调用取消
deadlineCtx,_ := context.WithDeadline(context.Background(),time.Now().Add(5*time.Second))
timeCtx,_:=context.WithTimeout(context.Background(),2*time.Second) //2s后取消
ctx := context.WithValue(context.Background(),1,2)
fmt.Println(ctx.Value(1)) // 2
detail code:
package main
import (
"context"
"fmt"
"sync"
"time"
)
/**
多个goruntine的并发控制实现
*/
func main(){
//func1()
//func2()
//func3()
//func4()
//withCancelTest()
//withDeadlineTest()
//withTimeTest()
withValueTest()
}
//channel + select + sleep 无缓存实现goruntine关闭控制
func func1(){
ch1:=make(chan bool)
ch2:=make(chan bool)
go Worker1(ch1)
go Worker2(ch2)
time.Sleep(3*1e9)
ch1<-true
time.Sleep(3*1e9)
ch2<-true
fmt.Println("main ending")
}
func Worker1(ch chan bool){
for{
select{
case <-ch:{
fmt.Println("Worker1: I was cancled by parent goruntinue")
return
}
default:
time.Sleep(1e9)
fmt.Println("Worker1: I am doing my work")
}
}
}
func Worker2(ch chan bool){
for{
select{
case <-ch:{
fmt.Println("Worker2: I was cancled by parent goruntinue")
return
}
default:
time.Sleep(1e9)
fmt.Println("Worker2: I am doing my work")
}
}
}
//channel + sleep 实现goruntine控制,此方法存在goruntine执行的不确定性
func func2(){
ch:=make(chan int)
go func()(){
for{
fmt.Println("Goroutine1:",<-ch)
}
}()
go func()(){
for{
fmt.Println("Goroutine2:",<-ch)
}
}()
ch<-1
ch<-1
time.Sleep(5*1e9)
fmt.Println("main goroutine ending!")
}
//Mutex加锁 + sleep 实现数据原子操作
type request struct{
message string
m sync.Mutex
}
func func3(){
req:=new(request)
req.message="helloworld"
go func(req *request)(){
req.m.Lock()
fmt.Println(req.message)
req.m.Unlock()
}(req)
go func(req *request)(){
req.m.Lock()
req.message="It's changed!"
req.m.Unlock()
}(req)
time.Sleep(3*1e9)
fmt.Println("main goroutine ending!")
}
//wg实现对平级的多个goruntine的管理控制
func func4(){
var wg sync.WaitGroup
wg.Add(2)
go func() {
fmt.Println("Routine1 is done")
wg.Done()
}()
go func() {
fmt.Println("Routine2 is done")
wg.Done()
}()
wg.Wait()//阻塞于此
fmt.Println("main goroutine ending!")
}
/**
context实现对复杂goroutine树的管理,即对goroutine的终结.
WithCancel本质:通过关闭channel来执行<-ctx.Done(),实现子goroutine执行结束。调用返回的方法cancelFunc关闭通道结束子goroutine.
*/
func withCancelTest(){
bg := context.Background()
cancelCtx,cancelFunc := context.WithCancel(bg)
go func(ctx context.Context) {
for {
select {
case <-ctx.Done(): //创建一个无缓存的只读channel,阻塞于此(canceler接口方法)
fmt.Println("child goroutine 1 work end! err:",ctx.Err())//关闭chann需要一个err理由
return
default:
time.Sleep(time.Second)
fmt.Println("child goroutine 1 working...")
}
}
}(cancelCtx)
go func(ctx context.Context) {
for {
select {
case <-ctx.Done(): //创建一个无缓存的只读channel,阻塞于此(canceler接口方法)
fmt.Println("child goroutine 2 work end! err:",ctx.Err())//关闭chann需要一个err理由
return
default:
time.Sleep(time.Second)
fmt.Println("child goroutine 2 working...")
cancelFunc()
}
}
}(cancelCtx)
time.Sleep(5*time.Second)
//调用方法,关闭channel通道,<-ctx.Done()才执行
//cancelFunc()
fmt.Println("main goroutine end")
}
/**
WithDeadline:到达(超过)指定时间后该context和其子孙context都将被取消,底层还是基于WithCancel,只是加了一个过期时间判断
*/
func withDeadlineTest(){
deadlineCtx,_ := context.WithDeadline(context.Background(),time.Now().Add(2*time.Second))//2s后(channel关闭,<-ctxDone()执行)结束goroutine
go func(ctx context.Context) {
fmt.Println("chile goroutine work...")
for{
select {
case <-ctx.Done():
fmt.Println("child goroutine work end! err: ",ctx.Err())
return
default:
time.Sleep(1 * time.Second)
fmt.Println("goroutine working...")
}
}
}(deadlineCtx)
fmt.Println("main goroutine work...")
time.Sleep(3 * time.Second)
fmt.Println("main goroutine end")
}
/**
WithTimeout:本质是对WithDeadline()方法的封装.功能为在当前时间的timeout时间后关闭channel,结束goroutine。
WithDeadline(parent, time.Now().Add(timeout))
*/
func withTimeTest(){
timeCtx,_:=context.WithTimeout(context.Background(),2*time.Second)//2s后停止goroutine
go func(ctx context.Context) {
for{
select {
case <-ctx.Done():
fmt.Println("goroutine work end。 err: ",ctx.Err())
return
default:
time.Sleep(1 * time.Second)
fmt.Println("goroutine working...")
}
}
}(timeCtx)
fmt.Println("main goroutine work...")
time.Sleep(3 * time.Second)
fmt.Println("main goroutine end")
}
/**
WithValue:用于存储一个key-value值的context对象,此对象可以作为withCancel函数的第一个参数传入使用。达到多个goroutine之间的数据使用作用
配合withCancel函数实现关闭channel,结束goroutine。
*/
func withValueTest() {
ctx := context.WithValue(context.Background(),1,2)
fmt.Println(ctx.Value(1))
}