Foundation to build the base
Thread programming language based on some of the design
ThreadGroup
ThreadGroup it is based on a commonly used concept of concurrent threads programming language, when a thread derive a sub-thread usually added to the parent thread's thread group (not specify a thread group), the last can be controlled set by ThreadGroup exit thread and other operations, and then go language goroutine no clear relationship between this parent / children, if you want to exit all goroutine on the current call chain will need to use context
ThreadLocal
In the thread-based programming language, language can usually be based on ThreadLocal to carry out some thread-local storage, is to key / value storage via a Map in nature and in go, there are no ThreadLocal design, passed in key / value when, in addition to the parameters to be passed through, it may be performed by the context transfer context information
context typical application scenarios
Scenes | achieve | principle |
---|---|---|
Contextual information transfer | WithValue | To save key pairs through key / value attribute an internal, can not be modified, can only be replaced by a cover worth of |
Notice of withdrawal | WithCancel | To inform common exit channel by listening notice |
Recursive context data acquisition
Because there is no use map in the context go inside data to be saved, so when the actual acquisition, is recursive up layer by layer from the beginning of the current layer, until it finds a matching key
In fact, we analogy ThreadGroup, because goroutine itself does not conceptually subordinate, but in fact we can achieve data transfer through parent-child relationship context, data can be set in a context goroutine, and then passed to the derived goroutine
Cancellation notice
既然通过context来构建parent/child的父子关系,在实现的过程中context会向parent来注册自身,当我们取消某个parent的goroutine, 实际上上会递归层层cancel掉自己的child context的done chan从而让整个调用链中所有监听cancel的goroutine退出
那如果一个child context的done chan为被初始化呢?那怎么通知关闭呢,那直接给你一个closedchan已经关闭的channel那是不是就可以了呢
带有超时context
如果要实现一个超时控制,通过上面的context的parent/child机制,其实我们只需要启动一个定时器,然后在超时的时候,直接将当前的context给cancel掉,就可以实现监听在当前和下层的额context.Done()的goroutine的退出
Background与TODO
Backgroud其实从字面意思就很容易理解,其实构建一个context对象作为root对象,其本质上是一个共享的全局变量,通常在一些系统处理中,我们都可以使用该对象作为root对象,并进行新context的构建来进行上下文数据的传递和统一的退出控制
那TODO呢?通常我们会给自己立很多的todo list,其实这里也一样,我们虽然构建了很多的todo list, 但大多数人其实啥也不会做,在很多的函数调用的过程中都会传递但是通常又不会使用,比如你既不会监听退出,也不会从里面获取数据,TODO跟Background一样,其背后也是返回一个全局变量
不可变性
通常我们使用context都是做位一个上下文的数据传递,比如一次http request请求的处理,但是如果当这次请求处理完成,其context就失去了意义,后续不应该继续重复使用一个context, 之前如果超时或者已经取消,则其状态不会发生改变
源码实现
context接口
type Context interface {
// Deadline返回一个到期的timer定时器,以及当前是否以及到期
Deadline() (deadline time.Time, ok bool)
// Done在当前上下文完成后返回一个关闭的通道,代表当前context应该被取消,以便goroutine进行清理工作
// WithCancel:负责在cancel被调用的时候关闭Done
// WithDeadline: 负责在最后其期限过期时关闭Done
// WithTimeout:负责超时后关闭done
Done() <-chan struct{}
// 如果Done通道没有被关闭则返回nil
// 否则则会返回一个具体的错误
// Canceled 被取消
// DeadlineExceeded 过期
Err() error
// 返回对应key的value
Value(key interface{}) interface{}
}
emptyCtx
emptyCtx是一个不会被取消、没有到期时间、没有值、不会返回错误的context实现,其主要作为context.Background()和context.TODO()返回这种root context或者不做任何操作的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"
}
比较有意思的实现时emptyCtx的String方法,该方法可以返回当前context的具体类型,比如是Background还是TODO, 因为background和todo是两个全局变量,这里通过取其地址来进行对应类型的判断
cancelCtx
结构体
cancelCtx结构体内嵌了一个Context对象,即其parent context,同时内部还通过children来保存所有可以被取消的context的接口,后续当当前context被取消的时候,只需要调用所有canceler接口的context就可以实现当前调用链的取消
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
Done操作返回当前的一个chan 用于通知goroutine退出
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
}
cancel
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
// context一旦被某个操作操作触发取消后,就不会在进行任何状态的修改
c.mu.Lock()
if c.err != nil {
c.mu.Unlock()
return // already canceled
}
c.err = err
if c.done == nil {
c.done = closedchan
} else {
// close当前chan
close(c.done)
}
// 调用所有children取消
for child := range c.children {
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
// 是否需要从parent context中移除,如果是当前context的取消操作,则需要进行该操作
// 否则,则上层context会主动进行child的移除工作
if removeFromParent {
removeChild(c.Context, c)
}
}
timerCtx
timerCtx主要是用于实现WithDeadline和WithTimer两个context实现,其继承了cancelCtx接口,同时还包含一个timer.Timer定时器和一个deadline终止实现
2.4.1 结构体
timerCtx
type timerCtx struct {
cancelCtx
timer *time.Timer // timer定时器
deadline time.Time //终止时间
}
取消方法
取消方法就很简单了首先进行cancelCtx的取消流程,然后进行自身的定时器的Stop操作,这样就可以实现取消了
func (c *timerCtx) cancel(removeFromParent bool, err error) {
c.cancelCtx.cancel(false, err)
if removeFromParent {
// Remove this timerCtx from its parent cancelCtx's children.
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
if c.timer != nil {
c.timer.Stop() // 停止定时器
c.timer = nil
}
c.mu.Unlock()
}
valueCtx
其内部通过一个key/value进行值得保存,如果当前context不包含着值就会层层向上递归
type valueCtx struct {
Context
key, val interface{}
}
func (c *valueCtx) String() string {
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
}
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}
propagateCancel
设计目标
propagateCancel主要设计目标就是当parent context取消的时候,进行child context的取消, 这就会有两种模式:
1.parent取消的时候通知child进行cancel取消
2.parent取消的时候调用child的层层递归取消
parentCancelCtx
context can be arbitrarily nested context tree structure composed of a N layer, the above two modes in combination, can be found when the parent is cancelCtx, timerCtx any one time, on the use of the second mode, the parent of the child to cancel call exit to complete the call chain, and vice versa using the first mode of listening Done
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
for {
switch c := parent.(type) {
case *cancelCtx:
return c, true // 找到最近支持cancel的parent,由parent进行取消操作的调用
case *timerCtx:
return &c.cancelCtx, true // 找到最近支持cancel的parent,由parent进行取消操作的调用
case *valueCtx:
parent = c.Context // 递归
default:
return nil, false
}
}
}
Core implementation
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
// 如果发现parent已经取消就直接进行取消
child.cancel(false, p.err)
} else {
if p.children == nil {
p.children = make(map[canceler]struct{})
}
// 否则加入parent的children map中
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
go func() {
select {
case <-parent.Done():
// 监听parent DOne完成, 此处也不会向parent进行注册
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
WithDeadline
With the above basic learning WithDeadline, it is a lot easier, WithDeadline will set a deadline, we need to wait for the current time can be calculated by how long it can be canceled
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
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,
}
// 监听parent的取消,或者向parent注册自身
propagateCancel(parent, c)
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()
if c.err == nil {
c.timer = time.AfterFunc(dur, func() {
// 构建一个timer定时器,到期后自动调用cancel取消
c.cancel(true, DeadlineExceeded)
})
}
// 返回取消函数
return c, func() { c.cancel(true, Canceled) }
}
Backgroup 与 ALL
In many calls in the underlying middleware are passed through the context information, which is the most common and Backgroup Todo, though they are based emptyCtx achieved, but more inclined Backgroup as a parent context for subsequent call chain context of the whole use root, and usually indicates that a subsequent TODO not carry out any operation, simply because the use of parameters to be passed
Original link http://www.sreguide.com/go/context.html
Micro Signal: baxiaoshi2020
concern Bulletin No. Read more source code analysis of the article
More Articles concern www.sreguide.com
article by a blog article multiple platforms OpenWrite release