原文鏈接:http://studygolang.com/articles/5131
簡(jiǎn)介##
Context是一個(gè)在go中經(jīng)常用到的程序包蝙昙,谷歌官方開(kāi)發(fā)嫩与。特別常見(jiàn)的一個(gè)應(yīng)用場(chǎng)景是有一個(gè)請(qǐng)求衍生出來(lái)的各個(gè)goroutine之間需要滿足一定的約束條件侨核,以實(shí)現(xiàn)一些諸如有效期岛都、終止routline樹(shù)、傳遞請(qǐng)求全局變量之類(lèi)的功能。使用Context實(shí)現(xiàn)上下文功能約定需要在你的方法中傳入?yún)?shù)的第一個(gè)傳入context.Context類(lèi)型的變量稚字,我們將通過(guò)源碼的閱讀和一些示例代碼來(lái)說(shuō)明context的用法。
核心接口##
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
默認(rèn)錯(cuò)誤:
var Canceled = errors.New("context canceled")
var DeadlineExceeded error = deadlineExceededError{}
type deadlineExceededError struct{}
func (deadlineExceededError) Error() string { return "context deadline exceeded" }
func (deadlineExceededError) Timeout() bool { return true }
func (deadlineExceededError) Temporary() bool { return true }
空Context
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
// struct{}, since vars of this type must have distinct addresses.
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"
}
源碼包中提供了兩個(gè)空Context
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context {
return background
}
// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter). TODO is recognized by static analysis tools that determine
// whether Contexts are propagated correctly in a program.
func TODO() Context {
return todo
}
canceler##
cancelCtx結(jié)構(gòu)體繼承了Context厦酬,實(shí)現(xiàn)了canceler方法:
//*cancelCtx 和 *timerCtx都實(shí)現(xiàn)了canceler接口胆描,實(shí)現(xiàn)該接口的類(lèi)型都可以直接被cancel
// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}
// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
Context
done chan struct{} // closed by the first cancel call.
mu sync.Mutex
children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
func (c *cancelCtx) Done() <-chan struct{} {
return c.done
}
func (c *cancelCtx) Err() error {
c.mu.Lock()
defer c.mu.Unlock()
return c.err
}
func (c *cancelCtx) String() string {
return fmt.Sprintf("%v.WithCancel", c.Context)
}
//核心是關(guān)閉c.done
//同時(shí)會(huì)設(shè)置c.err = err, c.children = nil
//依次遍歷c.children,每個(gè)child分別cancel
//如果設(shè)置了removeFromParent仗阅,則將c從其parent的children中刪除
// cancel closes c.done, cancels each of c's children, and, if
// removeFromParent is true, removes c from its parent's children.
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
close(c.done)
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)
}
}
再來(lái)看一些cancel相關(guān)的方法:
type CancelFunc func()
// WithCancel方法返回一個(gè)繼承自parent的Context對(duì)象昌讲,同時(shí)返回的cancel方法可以用來(lái)關(guān)閉返回的Context當(dāng)中的Done channel
// 其將新建立的節(jié)點(diǎn)掛載在最近的可以被cancel的父節(jié)點(diǎn)下(向下方向)
// 如果傳入的parent是不可被cancel的節(jié)點(diǎn),則直接只保留向上關(guān)系
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,
done: make(chan struct{}),
}
}
// 傳遞cancel
// 從當(dāng)前傳入的parent開(kāi)始(包括該parent)霹菊,向上查找最近的一個(gè)可以被cancel的parent
// 如果找到的parent已經(jīng)被cancel剧蚣,則將方才傳入的child樹(shù)給cancel掉
// 否則,將child節(jié)點(diǎn)直接連接為找到的parent的children中(Context字段不變旋廷,即向上的父親指針不變鸠按,但是向下的孩子指針變直接了)
//
// 如果沒(méi)有找到最近的可以被cancel的parent,即其上都不可被cancel饶碘,則啟動(dòng)一個(gè)goroutine等待傳入的parent終止目尖,則cancel傳入的child樹(shù),或者等待傳入的child終結(jié)扎运。
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 {
if p.children == nil {
p.children = make(map[canceler]bool)
}
p.children[child] = true
}
p.mu.Unlock()
} else {
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
// 從傳入的parent對(duì)象開(kāi)始瑟曲,依次往上找到一個(gè)最近的可以被cancel的對(duì)象,即cancelCtx或者timerCtx
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
for {
switch c := parent.(type) {
case *cancelCtx:
return c, true
case *timerCtx:
return &c.cancelCtx, true
case *valueCtx:
parent = c.Context
default:
return nil, false
}
}
}
//從parent開(kāi)始往上找到最近的一個(gè)可以cancel的父對(duì)象
// 從父對(duì)象的children map中刪除這個(gè)child
func removeChild(parent Context, child canceler) {
p, ok := parentCancelCtx(parent)
if !ok {
return
}
p.mu.Lock()
if p.children != nil {
delete(p.children, child)
}
p.mu.Unlock()
}
Deadline 和 TimeOut
首先看一個(gè)繼承自cancelCtx的結(jié)構(gòu)體:
type timerCtx struct {
cancelCtx //此處的封裝為了繼承來(lái)自于cancelCtx的方法豪治,cancelCtx.Context才是父親節(jié)點(diǎn)的指針
timer *time.Timer // Under cancelCtx.mu. 是一個(gè)計(jì)時(shí)器
deadline time.Time
}
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
return c.deadline, true
}
func (c *timerCtx) String() string {
return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
}
// 與cencelCtx有所不同洞拨,其除了處理cancelCtx.cancel,還回對(duì)c.timer進(jìn)行Stop()负拟,并將c.timer=nil
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()
}
由此結(jié)構(gòu)體衍生出的兩個(gè)方法:
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
// 如果parent的deadline比新傳入的deadline已經(jīng)要早烦衣,則直接WithCancel,因?yàn)樾聜魅氲膁eadline沒(méi)有效掩浙,父親的deadline會(huì)先到期花吟。
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: deadline,
}
// 接入樹(shù)
propagateCancel(parent, c)
// 檢查如果已經(jīng)過(guò)期,則cancel新的子樹(shù)
d := deadline.Sub(time.Now())
if d <= 0 {
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func() { c.cancel(true, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
// 還沒(méi)有被cancel的話厨姚,就設(shè)置deadline之后cancel的計(jì)時(shí)器
c.timer = time.AfterFunc(d, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
// timeout和deadline本質(zhì)一樣
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
Value
valueCtx主要用來(lái)傳遞一些元數(shù)據(jù)衅澈,通過(guò)WithValue()來(lái)傳入繼承,通過(guò)Value()來(lái)讀取谬墙,簡(jiǎn)單今布,不贅述.
func WithValue(parent Context, key interface{}, val interface{}) Context {
return &valueCtx{parent, key, val}
}
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)
}
使用原則:##
Programs that use Contexts should follow these rules to keep interfaces consistent across packages and enable static analysis tools to check context propagation:
使用Context的程序包需要遵循如下的原則來(lái)滿足接口的一致性以及便于靜態(tài)分析Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter, typically named ctx:
不要把Context存在一個(gè)結(jié)構(gòu)體當(dāng)中经备,顯式地傳入函數(shù)。Context變量需要作為第一個(gè)參數(shù)使用部默,一般命名為ctx
func DoSomething(ctx context.Context, arg Arg) error {
... use ctx ...
}
Do not pass a nil Context, even if a function permits it. Pass context.TODO if you are unsure about which Context to use.
即使方法允許弄喘,也不要傳入一個(gè)nil的Context,如果你不確定你要用什么Context的時(shí)候傳一個(gè)context.TODOUse context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.
使用context的Value相關(guān)方法只應(yīng)該用于在程序和接口中傳遞的和請(qǐng)求相關(guān)的元數(shù)據(jù)甩牺,不要用它來(lái)傳遞一些可選的參數(shù)The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines.
同樣的Context可以用來(lái)傳遞到不同的goroutine中,Context在多個(gè)goroutine中是安全的
使用要點(diǎn):##
其實(shí)本身非常簡(jiǎn)單累奈,在導(dǎo)入這個(gè)包之后贬派,初始化Context對(duì)象,在每個(gè)資源訪問(wèn)方法中都調(diào)用它澎媒,然后在使用時(shí)檢查Context對(duì)象是否已經(jīng)被Cancel搞乏,如果是就釋放綁定的資源。如下所示(下面的代碼截取自http://blog.golang.org/context中的示例程序)
初始化并設(shè)置最終cancel:
func handleSearch(w http.ResponseWriter, req *http.Request) {
// ctx is the Context for this handler. Calling cancel closes the
// ctx.Done channel, which is the cancellation signal for requests
// started by this handler.
var (
ctx context.Context
cancel context.CancelFunc
)
timeout, err := time.ParseDuration(req.FormValue("timeout"))
if err == nil {
// The request has a timeout, so create a context that is
// canceled automatically when the timeout expires.
ctx, cancel = context.WithTimeout(context.Background(), timeout)
} else {
ctx, cancel = context.WithCancel(context.Background())
}
defer cancel() // Cancel ctx as soon as handleSearch returns.
在中間過(guò)程傳遞戒努,并在資源相關(guān)操作時(shí)判斷是否ctx.Done()傳出了值请敦,關(guān)于Done()的使用應(yīng)該參考http://blog.golang.org/pipelines 意外終結(jié)的程序返回值應(yīng)該為對(duì)應(yīng)的ctx.Err()
func httpDo(ctx context.Context, req *http.Request, f func(*http.Response, error) error) error {
// Run the HTTP request in a goroutine and pass the response to f.
tr := &http.Transport{}
client := &http.Client{Transport: tr}
c := make(chan error, 1)
go func() { c <- f(client.Do(req)) }()
select {
case <-ctx.Done():
tr.CancelRequest(req)
<-c // Wait for f to return.
return ctx.Err()
case err := <-c:
return err
}
}