基盤を構築するための財団
デザインの一部に基づいて、スレッド・プログラミング言語
スレッドグループ
スレッドは通常、親スレッドグループに追加されたサブスレッドを導出するとき、それは言語を、プログラミング同時スレッドの一般的に使用される概念に基づいているスレッドグループの最後には、スレッドグループでセットを制御することができ、(スレッド・グループを指定しません)終了スレッドおよびその他の操作、および、言語のゴルーチンにこの親/子の間には明確な関係を行くない、あなたは現在の呼び出しチェーン上のすべてのゴルーチンを終了したい場合は、コンテキストを使用する必要があります
ThreadLocalの
スレッドベースのプログラミング言語では、言語は通常、いくつかのスレッドローカルストレージを実施することにThreadLocalに基づくことができる、自然の中や、外出先で地図を経由して、キー/値のストレージに、何のThreadLocalのデザインはありませんが、キー/値に渡されました通過するパラメータに加えて、コンテキスト転送コンテキスト情報によって実行することができる場合、
コンテキスト代表的なアプリケーション・シナリオ
シーン | 実現 | 原則 |
---|---|---|
コンテキスト情報の転送 | WithValue | 内部キー/値の属性を使用して鍵ペアを保存するには、変更できない唯一のカバーの価値に置き換えることができます |
撤退のお知らせ | WithCancel | 通知を聞くことによって、共通の出口チャネルを通知するために、 |
再帰コンテキストデータの取得
何の使用マップは、保存するデータ内の文脈の回ではありませんので、それが一致するキーが見つかるまで実際の取得は、現在のレイヤーの最初から層によって再帰アップ層である場合
実際には、我々類推のThreadGroup、ゴルーチン自体は概念的に従属しませんが、実際には、我々はデータがコンテキストゴルーチンに設定し、その後、得られたゴルーチンに渡すことができ、親子関係のコンテキストを介してデータ転送を実現することができるので、
キャンセル通知
既然通过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
呼び出しをキャンセルするコンテキスト親cancelCtx、timerCtxである場合の組合せで二つのモードの上方に、N層からなる任意のネストされた文脈木構造とすることができ、第二のモードの使用には、いずれかの時間を見つけることができ、子供の親呼び出しチェーンを完了するための出口、および完了リスニングの最初のモードを使用して、その逆
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
}
}
}
コア実装
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
上記の基本的な学習WithDeadlineでは、それは非常に簡単で、WithDeadlineは期限を設定します、我々はそれをキャンセルすることができますどのくらいの期間で計算することができ、現在の時間を待つ必要が
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
多くのコールでは根本的なミドルウェアでそれらがemptyCtxをベースとしているが、最も一般的でBackgroup藤堂あるコンテキスト情報、通過した達成が、より全体のその後の呼び出しチェーンコンテキストの親コンテキストとしてBackgroupを傾けていますルートを使用し、通常のパラメータの使用を通過するという理由だけで、その後のTODOは、任意の動作を実行しないことを示し
オリジナルリンクhttp://www.sreguide.com/go/context.html
マイクロ・シグナル:baxiaoshi2020の
記事の懸念会報第もっと読むソースコード解析
より多くの記事の懸念www.sreguide.com
ブログ記事複数のプラットフォームによる記事OpenWriteリリース