Golang context

Context

Go 语言中提供了 context 包,通过显示传递 context, 实现请求级别的元数据、取消信号、终止信号的传递。context 包提供了从现有的上下文值(curContext)派生新的上下文值(newContext)的函数。 这些值会形成一个树。 当一个 context 被取消或者超时时,从它派生的所有 context 也都被取消。利用这个特性可以实现服务请求调用的超时控制。当一个请求被取消或超时时,处理该请求的所有 goroutine 都可以快速退出(fail fast),这样系统就可以回收它们正在使用的资源。

从 Go 项目的编程风格看,一种常见的作法是将 context 作为函数的第一个参数,通过显示传递的方式,贯穿请求的全流程。这样做的代价是所有的函数入参都将带上 context 信息 (trade-off)。

需要注意的是,context 通常是面向请求的,所以在使用 context 传递的数据一般是指请求的上下文信息,比如ip、traceId、用户信息等。

Context 接口约定了5个方法

// A Context carries a deadline, cancellation signal, and request-scoped values
// across API boundaries. Its methods are safe for simultaneous use by multiple
// goroutines.

type Context interface {
    // Done returns a channel that is closed when this Context is canceled
    // or times out.

    Done() <-chan struct{} err indicates why this context was canceled, after the done channel is closed. err() error deadline returns time when will be if any. deadline() (deadline time.time, ok bool) value associated with key or nil none. value(key interface{}) interface{} } < code></-chan>
  • Deadline 方法返回当前 context 被取消的时间
  • Done 方法返回一个 channel,这个 channel 会在当前工作完成或者上下文被取消之后关闭,多次调用Done方法会返回同一个channel
  • Err方法会返回当前 context 结束的原因,它只会在Done返回的 context 被关闭时才会返回非空的值,如果当前Context被取消就会返回 canceled 错误,如果当前 context 超时就会返回 DeadlineExceeded 错误
  • Value方法会从 context 中返回键对应的值,对于同一个上下文来说,多次调用 Value 并传入相同的 key会返回相同的结果

Derived contexts

context 包提供了很多派生方法的实现。譬如 WithCancel、WithTimeout 等。以下是 context 包的函数定义。

// Background returns an empty Context. It is never canceled, has no deadline,
// and has no values. Background is typically used in main, init, and tests,
// and as the top-level Context for incoming requests.

func Background() Context

// 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) {}

// A CancelFunc cancels a Context.

type CancelFunc func()

// 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 WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {}

// WithValue returns a copy of parent in which the value associated with key is
// val.

//
// Use context Values only for request-scoped data that transits processes and
// APIs, not for passing optional parameters to functions.

func WithValue(parent Context, key, val interface{}) Context {}

// A valueCtx carries a key-value pair. It implements Value for that key and
// delegates all other calls to the embedded Context.

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

// WithDeadline returns a copy of the parent context with the deadline adjusted
// to be no later than d. If the parent's deadline is already earlier than d,
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
// context's Done channel is closed when the deadline expires, when the returned
// cancel function is called, or when the parent context's Done channel is
// closed, whichever happens first.

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {}

值得注意的时,WithValue 一对 key/value 时,它将父节点的 context 嵌入到子 context, 并在节点中保存 key/value 数据。Value() 查询 key 对应的数据时,会先从当前 context 查询,如果查询不到,会递归查询父 context 中的数据。所以 WithValue 实际上类似一个链表,不适合大量使用。

Context 使用示例

func get(ctx context.Context) <-chan int { num :="make(chan" int) n go func() for select case <-ctx.done(): fmt.println(ctx.err().error()) context canceled close(num) 需要关闭 channel 否则如果还有消费者消费会死锁 return return结束该goroutine,防止泄露 <- n: n++ } }() func main() ctx, cancel number fmt.println(<-number) cancel() < code></-chan>
var wg sync.WaitGroup

func worker(ctx context.Context) {
    i := 1
Loop:
    for i < 1000 {
        i++
        fmt.Println("i=", i)
        time.Sleep(time.Millisecond * 20)
        select {
        case <-ctx.done(): fmt.println(ctx.err().error()) context deadline exceeded break loop default: } wg.done() func main() { ctx, cancel :="context.WithTimeout(context.Background()," time.millisecond*50) wg.add(1) go worker(ctx) time.sleep(time.second * 1) cancel() wg.wait() fmt.println("over") < code></-ctx.done():>
func main() {
    d := time.Now().Add(50 * time.Millisecond)
    ctx, cancel := context.WithDeadline(context.Background(), d)
    defer cancel()

    for {
        select {
        case <-time.after(10 * time.millisecond): fmt.println("next") case <-ctx.done(): fmt.println(ctx.err().error()) context deadline exceeded return } < code></-time.after(10>
const (
    KEY_CODE = "demo"
)

var w sync.WaitGroup

func worker1(ctx context.Context) {
    key := KEY_CODE
    traceCode, ok := ctx.Value(key).(string)
    if !ok {
        fmt.Println("invalid")
    }
    for {
        fmt.Printf("worker1, trace code:%s\n", traceCode)
        time.Sleep(time.Millisecond * 10)
        select {
        case <-ctx.done(): fmt.println(ctx.err().error()) w.done() return default: } func main() { ctx, cancel :="context.WithTimeout(context.Background()," time.millisecond*50) ctx="context.WithValue(ctx," key_code, "123456") w.add(1) go worker1(ctx) time.sleep(time.second * 1) cancel() w.wait() fmt.println("over") < code></-ctx.done():>

Original: https://www.cnblogs.com/arvinhuang/p/16437913.html
Author: 平凡键客
Title: Golang context

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/683998/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球