上一篇分析链接池的时候,看到了一个ctx, cancel := context.WithCancel(context.Background()) 这个操作,这个是使用了context库,分析下这个库的作用和源码
-
context是用来做什么了 我们的main主协程启动了一个go协程,由于这个协程运行了太久的时候,我们想取消这个启动的协程,可以使用channel的方式,还可以使用的就是这个context方法,context就是记录当前的上下文
-
context的使用
//手动取消
func main() {
//获取上下文
ctx,cancel := context.WithCancel(context.Background())
//给上下文传递添加的值
withValue :=context.WithValue(ctx,"xiaobai","add value")
go watch(withValue)
time.Sleep(10*time.Second)
//发送取消事件
cancel()
time.Sleep(3*time.Second)
}
func watch(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println(ctx.Value("xiaobai"),"is cancel")
return
default:
fmt.Println(ctx.Value("xiaobai"),"is goruntine")
time.Sleep(2*time.Second)
}
}
}
- 为什么执行了cancel函数,就会自动取消go协程,下面看看context的源码
type Context interface {
//记录运行的时间
Deadline() (deadline time.Time, ok bool)
//当停止的时候 发送channel
Done() <-chan struct{}
//错误信息
Err() error
//传递一个interface变量
Value(key interface{}) interface{}
}
- go的context包实现了上面的方法,接下来看看context的使用
//使用
context.WithCancel(context.Background())
//在这里返回了一个c.canel的函数,外部使用这个cancel函数就可以发送关闭请求
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
//创建一个新的链接
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
//创建一个对象
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}
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 {
//如果不存在 创建并且保存map
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():
}
}()
}
}
//外部调用这个函数,发送关闭请求
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
//如果c.done是空的话,复制closechan
if c.done == nil {
c.done = closedchan
} else {
//关闭了c.done
close(c.done)
}
//循环删除所有的children
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
//是否删除父类
if removeFromParent {
removeChild(c.Context, c)
}
}
通过上面的代码,当我们在外部执行了cancel的时候,会close(d.done),当收到c.done的请求的时候,就可以关闭当前的协程
其他的几种方式,其实源码也是一样的
//超时自动截至
var wg sync.WaitGroup
func main() {
ctx ,cancel := context.WithTimeout(context.Background(),time.Second*4)
defer cancel()
fmt.Println("I am goint to work")
times,ok := ctx.Deadline()
fmt.Println("time",times)
fmt.Println("ok",ok)
//未获取到异常
err := ctx.Err()
fmt.Println(err)
wg.Add(1)
go worker(ctx)
wg.Wait()
times,ok = ctx.Deadline()
fmt.Println("time",times)
fmt.Println("ok",ok)
//获取到异常
err = ctx.Err()
fmt.Println(err)
fmt.Println("finshed goint to work")
}
func worker(ctx context.Context) error {
defer wg.Done()
for i:=0;i<1000;i++ {
select {
case <-time.After(time.Second*1):
fmt.Println("运行了多少次",i)
fmt.Println("dong some work")
case <-ctx.Done():
fmt.Println("Cancel the content",i)
//结束运行 抛出一个错误
return ctx.Err()
}
}
return nil
}
//截至时间 自动停止
func main() {
times := time.Now().Add(time.Second * 10)
ctx, cancel := context.WithDeadline(context.Background(), times)
defer cancel()
for {
select {
case <-time.After(2*time.Second):
fmt.Println("oversleep")
case <-ctx.Done():
fmt.Println(ctx.Err())
return
}
}
}