概述
context源码分析
Golang JDK 1.10.3
Context介绍
假如有多个goroutine来处理一个操作,这个操作有超时时间,当超时时间到了之后,如何通知这个操作的所有goroutine都退出。或者在操作的过程中有一个goroutine遇到异常需要退出,如何通知其他的goroutine也退出该操作? context就可以很好的解决以上问题。
Go服务器的每个请求都有自己的goroutine,而有的请求为了提高性能,会经常启动额外的goroutine处理请求,当该请求被取消或超时,为了防止资源泄露,该请求上的所有goroutines应该退出。那么context来了,它对该请求上的所有goroutines进行约束,然后进行取消信号,超时等操作。
context优点就是简洁的管理goroutines的生命周期。
Context源码分析
1. Context接口
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Dealine()方法 返回当前上下文的截至时间,即该上下文应该被取消的时间,如果没有设置超时时间,则返回的ok为false
Done()方法 在Context被取消或超时时会close当前监听的channel,close的channel可以作为广播通知,告诉context相关的函数要停止当前工作然后退出。
创建Context时会返回CancelFunc函数,调用CancelFunc函数以及到了超时时间,会关闭Done()方法监听的channel,所以Done()需要放到select中。
父Context取消时,其子Context也会被取消。但是子Context取消时,其父Context不会被取消。
Err()方法 如果Done未关闭,则Err返回nil,如果Done已关闭,则返回非空的error解释原因。
Value方法通过key获取该key在上下文中关联的值,如果该key没有关联的值,则返回nil,多次调用同一个key,返回的结果是一样的。
2. emptyCtx
empty是空的Context,但是实现了Context的接口。emptyCtx没有超时时间,不能被取消,也不能存储任何额外信息,所以emptyCtx常用来作为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"
}
func Background() Context {
return background
}
func TODO() Context {
return todo
}
由于emptyCtx是不能外部访问,所以我们只能用Background和TODO来使用。
Background经常会被使用,经常被当成根节点使用,而TODO在不确定使用什么Context时才会使用。
以下使用Background的示例,作为创建出来ctx的父节点
ctx, cancel := context.WithCancel(context.Background())
3.cancelCtx
cancelCtx结构体实现了Context接口,同时也实现了canceler接口,cancelCtx是可以被取消的Context实现,也是context包的核心结构体。当cacel时,它会取消所有子节点中所有实现canceler接口的context。
type cancelCtx struct {
Context //指定父级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
}
Context是指定父级Context
done 当调用取消函数或超时时,会通过close该channel,来通知上下文。
childer 记录所有实现canceler的子节点
err 第一次调用cancel后会设置一个非空的error,后面可以判断该值是否为空,来判断该上下文是否已经被取消
cancelCtx相关方法
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
}
Done()方法其实就是返回的该结构体中的done字段
func (c *cancelCtx) Err() error {
c.mu.Lock()
defer c.mu.Unlock()
return c.err
}
返回错误信息
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
if c.done == nil { //如果c.done为nil,则初始化一个closed的chan
c.done = closedchan
} else { //不为空则关闭
close(c.done)
}
for child := range c.children { //cancel该context c的所有字节
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)
}
c.children = nil //将该context的children置为nil
c.mu.Unlock()
if removeFromParent { //是否删除其parent's context 的children中删除c
removeChild(c.Context, c)
}
}
该cancel方法是取消上下文的实现逻辑
(1). 取消当前节点的上下文
如果当前节点的done为nil,则直接赋值一个关闭的channel。监听一个关闭的channel会直接返回。如果done不为nil,则close。
var closedchan = make(chan struct{})
func init() {
close(closedchan)
}
closedchan初始化之后,就直接close。这样当监听该close 的channel时就会直接返回。
closedchan示例:
package main
import (
"fmt"
)
func main() {
ch := make(chan struct{})
close(ch)
select {
case v, ok := <-ch:
fmt.Println("ch")
fmt.Println(v)
fmt.Println(ok)
default:
fmt.Println("default")
}
}
该示例会走 <-ch case
(2). 调用该节点的所有子节点的cancel来取消子节点,以及子节点的子节点.....
(3). removeFromParent为true,则会删除当前节点维护的子节点信息。
4.timerCtx结构体
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time //超时时间点
}
从结构体中我们看到,timerCtxt继承了cancelCtx,是在cancelCtx结构体中提供了进一步的扩展。timerCtx可以调用取消函数进行取消,也可以设置超时,当超时时自动取消。
timer 是超时时执行的取消函数
deadline 记录超时的时间点。
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { //返回deadline,是否创建deadline
return c.deadline, true
}
Deadline方法用于返回超时的时间点
func (c *timerCtx) cancel(removeFromParent bool, err error) {
c.cancelCtx.cancel(false, err) //取消该上下文
if removeFromParent { //删除该cancelCtx的parent的Context的children c
// Remove this timerCtx from its parent cancelCtx's children.
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
if c.timer != nil { //如果timer不为nil,则停止,避免重复执行cancel
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}
cancel方法主要调用cancelCtx的cancel进行取消context。
如果timer不为nil,则停止该定时任务,并置为nil。
5.初始化Context
上面我们提到Background()函数可以返回emptyCtx对象,常用来作为根节点。
其他对外提供的Context初始化方法为:WithCancel、WithDeadline和WithTimeout。
(1). 通过WithCancel初始化Context
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent) //初始化一个cancelCtx,并指定父级Context为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 { //如果parent的Done()为nil,说明父级没有cancel,直接返回
return // parent is never canceled
}
if p, ok := parentCancelCtx(parent); ok { //parent的Contex实现类是cancelCtx
p.mu.Lock()
if p.err != nil { //parent已经被取消
// parent has already been canceled
child.cancel(false, p.err)
} else {
if p.children == nil { //如果p的children未初始化,则进行初始化
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{} //指定改parent的children
}
p.mu.Unlock()
} else { //parent的Contex实现类不是是cancelCtx
go func() {
select {
case <-parent.Done(): //当parent关闭时,关闭children
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
WithCancel方法首先调用newCancelCtx初始化一个cancelCtx对象,并指定该对象的Context。
propagateCancel方法的主要功能是如果指定的父节点是cancelCtx类型的,则将当前创建的节点维护到父节点中(通过父节点的children关联)。并且实现当父节点取消时,当前子节点也会被取消。
(2). 通过WithDeadline初始化Context
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
//ok:是否设置deadline,cur设置超时时间
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// The current deadline is already sooner than the new one.
//设置超时时间 晚于 父级parent的超时时间,则调用WithCancel(parent)创建
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent), //初始化cancelCtx
deadline: d, //记录超时时间点
}
propagateCancel(parent, c) //实现子级与父级关联,当父级cancel时,子级也cancel
dur := time.Until(d) //获取d到当前时间的duration
if dur <= 0 { //dur 小于 0,超时时间已过
c.cancel(true, DeadlineExceeded) // deadline 已过,调用cancel函数取消
return c, func() { c.cancel(true, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil { //到dur时间之后,调用c.cancel()函数,由系统新建goroutine执行
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
WithDeadline方法创建的Context,不仅可以通过手动调用取消函数取消,也可以指定超时的时间点,如果超时则会自动取消。主要通过timerCtx实现以上取消功能。
1. 如果创建的子节点的超时时间比父节点的超时时间还晚,则无需使用timerCtx创建,因为父节点取消时,该节点也会创建。所以直接调用WithCancel创建。
2. 如果晚于父节点,则会创建timerCtx对象,并调用propagateCancel实现与父节点的关联。
3. 如果超时时间已到,则直接调用取消函数。否则开启一个定时器到超时时间点触发取消函数。
(3). 通过WithDeadline初始化Context
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
WithTimeout 等同于 WithDeadline(parent, time.Now().Add(timeout))
最后
以上就是眯眯眼云朵为你收集整理的Golang context包源码分析的全部内容,希望文章能够帮你解决Golang context包源码分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复