- ある程度はchannelの時にやってるんですが
- いざcontextつかって良い感じにやろうと思うと
- 何が正着だっけという感じで迷うので調べ直しました
基本的なところ
Context
DeadLine
,Done
,Err
,Value
を要求するinterface
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
context.Background()
- 最初にcontextを生成する為に呼ばれるメソッド
ctx := context.Background()
- これの正体がなんなのかというと
- contextパッケージにいるbackgroudを返している
func Background() Context {
return background
}
- じゃあbackgroudは何かというと
emptyCtx
がnewされた物が入っている
background = new(emptyCtx)
- emptyCtxはただのintです(型はemptyCtxなんですが)
- 要求されたメソッドはすべて実装されていますが全部偽を返します
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
}
- 試しにこのBackgroudで生成して
- goroutineの無限ループに渡してselectで待ち受けると
- nilが返ってくるのでブロックしているはずです
- 公式によると
context.WithCancel()
- cancelメソッドを返してくれる
- 要は中のdoneを操作してcancelを行っている
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
newCancelCtx
を作成する
- これは、
cancelCtx
型を返している
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}
- cancelCtx型は以下の定義
- Deadlineは埋め込みで回避しているはず
type cancelCtx struct {
Context
mu sync.Mutex
done chan struct{}
children map[canceler]struct{}
err error
}
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は偽が返るselectが実行されても終わらない
- 公式のここのサンプルが参考になる
- それで、最終的に
cancel
が呼ばれた際に
- 内部メソッドのcancelが呼ばれる
- doneをcloseさせ、childrenにも再帰的にcancelをかける
- 最後に親からremoveして終わる
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
}
c.err = err
if c.done == nil {
c.done = closedchan
} else {
close(c.done)
}
for child := range c.children {
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
if removeFromParent {
removeChild(c.Context, c)
}
}
- closedchanが何故closedなのか最初は分からなかったが
- コードを見つけて分かった
- initでcloseしていた
var closedchan = make(chan struct{})
func init() {
close(closedchan)
}