Go语言设计与实现 -- 上下文

我们先来看一下context.Context的接口:

// A Context carries a deadline, a cancellation signal, and other values across
// API boundaries.
//
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface {
   // Deadline returns the time when work done on behalf of this context
   // should be canceled. Deadline returns ok==false when no deadline is
   // set. Successive calls to Deadline return the same results.
   Deadline() (deadline time.Time, ok bool)

   // Done returns a channel that's closed when work done on behalf of this
   // context should be canceled. Done may return nil if this context can
   // never be canceled. Successive calls to Done return the same value.
   // The close of the Done channel may happen asynchronously,
   // after the cancel function returns.
   //
   // WithCancel arranges for Done to be closed when cancel is called;
   // WithDeadline arranges for Done to be closed when the deadline
   // expires; WithTimeout arranges for Done to be closed when the timeout
   // elapses.
   //
   // Done is provided for use in select statements:
   //
   //  // Stream generates values with DoSomething and sends them to out
   //  // until DoSomething returns an error or ctx.Done is closed.
   //  func Stream(ctx context.Context, out chan<- Value) error {
   //     for {
   //        v, err := DoSomething(ctx)
   //        if err != nil {
   //           return err
   //        }
   //        select {
   //        case <-ctx.Done():
   //           return ctx.Err()
   //        case out <- v:
   //        }
   //     }
   //  }
   //
   // See https://blog.golang.org/pipelines for more examples of how to use
   // a Done channel for cancellation.
   Done() <-chan struct{}

   // If Done is not yet closed, Err returns nil.
   // If Done is closed, Err returns a non-nil error explaining why:
   // Canceled if the context was canceled
   // or DeadlineExceeded if the context's deadline passed.
   // After Err returns a non-nil error, successive calls to Err return the same error.
   Err() error

   // Value returns the value associated with this context for key, or nil
   // if no value is associated with key. Successive calls to Value with
   // the same key returns the same result.
   //
   // Use context values only for request-scoped data that transits
   // processes and API boundaries, not for passing optional parameters to
   // functions.
   //
   // A key identifies a specific value in a Context. Functions that wish
   // to store values in Context typically allocate a key in a global
   // variable then use that key as the argument to context.WithValue and
   // Context.Value. A key can be any type that supports equality;
   // packages should define keys as an unexported type to avoid
   // collisions.
   //
   // Packages that define a Context key should provide type-safe accessors
   // for the values stored using that key:
   //
   //     // Package user defines a User type that's stored in Contexts.
   //     package user
   //
   //     import "context"
   //
   //     // User is the type of value stored in the Contexts.
   //     type User struct {...}
   //
   //     // key is an unexported type for keys defined in this package.
   //     // This prevents collisions with keys defined in other packages.
   //     type key int
   //
   //     // userKey is the key for user.User values in Contexts. It is
   //     // unexported; clients use user.NewContext and user.FromContext
   //     // instead of using this key directly.
   //     var userKey key
   //
   //     // NewContext returns a new Context that carries value u.
   //     func NewContext(ctx context.Context, u *User) context.Context {
   //        return context.WithValue(ctx, userKey, u)
   //     }
   //
   //     // FromContext returns the User value stored in ctx, if any.
   //     func FromContext(ctx context.Context) (*User, bool) {
   //        u, ok := ctx.Value(userKey).(*User)
   //        return u, ok
   //     }
   Value(key any) any
}

我们来逐一解释一下这些方法的含义:

  • Deadline – 返回context.Context被取消的时间,即完成工作的截止日期
  • Done – 返回一个Channel,这个Channel会在当前工作完成或上下文被取消之后关闭,多次调用该方法会返回同一个Channel
  • Err返回Context结束的原因
  • Valuecontext.Context中获取键对应的值,对于同一个上下文来说,多次调用Value并传入相同的key会返回相同的结果,该方法可以用来传递特定的数据

设计原理

golang-context-usage

context.Context的最大作用是,在Goroutine构成的树形结构中同步信号以减少计算资源的浪费。每一个context.Context都会从最顶层的Goroutine逐层传递到最底层。

下面我们用一个事例来看一下:

在讲解事例之前我们先来认识一下context.WithTimeout函数:

// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete:
//
//     func slowOperationWithTimeout(ctx context.Context) (Result, error) {
//        ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
//        defer cancel()  // releases resources if slowOperation completes before timeout elapses
//        return slowOperation(ctx)
//     }
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
   return WithDeadline(parent, time.Now().Add(timeout))
}

我们创建一个过期时间为1s的上下文,并向上下文传入handle函数,它处理请求的时间是500ms

func main() {
   ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    // 为什么要写这一句话?原因很简单,如果上下文到达了它之前规定的超时时间,它会发送Done信号,然后消耗释放上下文资源,但是如果在超时之前,程序就执行完毕了,那么就会造成内存泄漏,因此要加上这句话,确保可以释放上下文的内存
   defer cancel()

   go handle(ctx, 500*time.Millisecond)
   select {
   case <-ctx.Done():
      fmt.Println("main", ctx.Err())
   }
}

func handle(ctx context.Context, duration time.Duration) {
   select {
   case <-ctx.Done():
      fmt.Println("handle", ctx.Err())
   case <-time.After(duration):
      fmt.Println("process request with", duration)
   }
}

因为过期时间大于处理时间,所以我们有足够时间处理请求,将打印以下信息。

process request with 500ms
main context deadline exceeded

如果把处理请求的时间换成1500ms,那么整个程序会因为上下文过期而终止。

取消信号

context.WithCancel函数能够从context.Context中衍生出新的子上下文,并返回用于取消该上下文的函数。一旦我们执行返回的取消函数,当前上下文及其子上下文都会被取消,所有Goroutine都会同步收到这一取消信号。

// WithCancel returns a copy of parent with a new Done channel. The returned
// context's Done channel is closed when the returned cancel function is called
// or when the parent context's Done channel is closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	c := newCancelCtx(parent)
	propagateCancel(parent, &c)
	return &c, func() { c.cancel(true, Canceled) }
}

传值方法

在最后我们需要了解如何使用上下文传值,context 包中的 context.WithValue 能从父上下文中创建一个子上下文,传值的子上下文使用 context.valueCtx 类型:

func WithValue(parent Context, key, val interface{}) Context {
	if key == nil {
		panic("nil key")
	}
	if !reflectlite.TypeOf(key).Comparable() {
		panic("key is not comparable")
	}
	return &valueCtx{parent, key, val}
}

context.valueCtx 结构体会将除了 Value 之外的 ErrDeadline 等方法代理到父上下文中,它只会响应 context.valueCtx.Value 方法,该方法的实现也很简单:

type valueCtx struct {
	Context
	key, val interface{}
}

func (c *valueCtx) Value(key interface{}) interface{} {
	if c.key == key {
		return c.val
	}
	return c.Context.Value(key)
}

如果 context.valueCtx 中存储的键值对与 context.valueCtx.Value 方法中传入的参数不匹配,就会从父上下文中查找该键对应的值直到某个父上下文中返回 nil 或者查找到对应的值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胡桃姓胡,蝴蝶也姓胡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值