乐趣区

关于go:Go-语言-context-都能做什么

原文链接: Go 语言 context 都能做什么?

很多 Go 我的项目的源码,在读的过程中会发现一个很常见的参数 ctx,而且根本都是作为函数的第一个参数。

为什么要这么写呢?这个参数到底有什么用呢?带着这样的疑难,我钻研了这个参数背地的故事。

开局一张图:

外围是 Context 接口:

// A Context carries a deadline, cancelation 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 the time when this Context will be canceled, if any.
    Deadline() (deadline time.Time, ok bool)

    // Value returns the value associated with key or nil if none.
    Value(key interface{}) interface{}}

蕴含四个办法:

  • Done():返回一个 channel,当 times out 或者调用 cancel 办法时。
  • Err():返回一个谬误,示意勾销 ctx 的起因。
  • Deadline():返回截止工夫和一个 bool 值。
  • Value():返回 key 对应的值。

有四个构造体实现了这个接口,别离是:emptyCtx, cancelCtx, timerCtxvalueCtx

其中 emptyCtx 是空类型,裸露了两个办法:

func Background() Context
func TODO() Context

个别状况下,会应用 Background() 作为根 ctx,而后在其根底上再派生出子 ctx。要是不确定应用哪个 ctx,就应用 TODO()

另外三个也别离裸露了对应的办法:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context

遵循规定

在应用 Context 时,要遵循以下四点规定:

  1. 不要将 Context 放入构造体,而是应该作为第一个参数传入,命名为 ctx
  2. 即便函数容许,也不要传入 nil 的 Context。如果不晓得用哪种 Context,能够应用 context.TODO()
  3. 应用 Context 的 Value 相干办法只应该用于在程序和接口中传递和申请相干的元数据,不要用它来传递一些可选的参数。
  4. 雷同的 Context 能够传递给不同的 goroutine;Context 是并发平安的。

WithCancel

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

WithCancel 返回带有新 Done 通道的父级正本。当调用返回的 cancel 函数或敞开父上下文的 Done 通道时,返回的 ctxDone 通道将敞开。

勾销此上下文会开释与其关联的资源,因而在此上下文中运行的操作实现后,代码应立即调用 cancel

举个例子:

这段代码演示了如何应用可勾销上下文来避免 goroutine 透露。在函数完结时,由 gen 启动的 goroutine 将返回而不会透露。

package main

import (
    "context"
    "fmt"
)

func main() {
    // gen generates integers in a separate goroutine and
    // sends them to the returned channel.
    // The callers of gen need to cancel the context once
    // they are done consuming generated integers not to leak
    // the internal goroutine started by gen.
    gen := func(ctx context.Context) <-chan int {dst := make(chan int)
        n := 1
        go func() {
            for {
                select {case <-ctx.Done():
                    return // returning not to leak the goroutine
                case dst <- n:
                    n++
                }
            }
        }()
        return dst
    }

    ctx, cancel := context.WithCancel(context.Background())
    defer cancel() // cancel when we are finished consuming integers

    for n := range gen(ctx) {fmt.Println(n)
        if n == 5 {break}
    }
}

输入:

1
2
3
4
5

WithDeadline

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

WithDeadline 返回父上下文的正本,并将截止日期调整为不晚于 d。如果父级的截止日期曾经早于 d,则 WithDeadline(parent, d) 在语义上等同于 parent

当截止工夫到期、调用返回的勾销函数时或当父上下文的 Done 通道敞开时,返回的上下文的 Done 通道将敞开。

勾销此上下文会开释与其关联的资源,因而在此上下文中运行的操作实现后,代码应立即调用勾销。

举个例子:

这段代码传递具备截止工夫的上下文,来通知阻塞函数,它应该在达到截止工夫时立即退出。

package main

import (
    "context"
    "fmt"
    "time"
)

const shortDuration = 1 * time.Millisecond

func main() {d := time.Now().Add(shortDuration)
    ctx, cancel := context.WithDeadline(context.Background(), d)

    // Even though ctx will be expired, it is good practice to call its
    // cancellation function in any case. Failure to do so may keep the
    // context and its parent alive longer than necessary.
    defer cancel()

    select {case <-time.After(1 * time.Second):
        fmt.Println("overslept")
    case <-ctx.Done():
        fmt.Println(ctx.Err())
    }
}

输入:

context deadline exceeded

WithTimeout

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

WithTimeout 返回 WithDeadline(parent, time.Now().Add(timeout))

勾销此上下文会开释与其关联的资源,因而在此上下文中运行的操作实现后,代码应立即调用勾销。

举个例子:

这段代码传递带有超时的上下文,以通知阻塞函数应在超时后退出。

package main

import (
    "context"
    "fmt"
    "time"
)

const shortDuration = 1 * time.Millisecond

func main() {
    // Pass a context with a timeout to tell a blocking function that it
    // should abandon its work after the timeout elapses.
    ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
    defer cancel()

    select {case <-time.After(1 * time.Second):
        fmt.Println("overslept")
    case <-ctx.Done():
        fmt.Println(ctx.Err()) // prints "context deadline exceeded"
    }

}

输入:

context deadline exceeded

WithValue

func WithValue(parent Context, key, val any) Context

WithValue 返回父级的正本,其中与 key 关联的值为 val

其中键必须是可比拟的,并且不应是字符串类型或任何其余内置类型,以防止应用上下文的包之间发生冲突。WithValue 的用户应该定义本人的键类型。

为了防止调配给 interface{},上下文键通常具备具体的 struct{} 类型。或者,导出的上下文键变量的动态类型应该是指针或接口。

举个例子:

这段代码演示了如何将值传递到上下文以及如何检索它(如果存在)。

package main

import (
    "context"
    "fmt"
)

func main() {
    type favContextKey string

    f := func(ctx context.Context, k favContextKey) {if v := ctx.Value(k); v != nil {fmt.Println("found value:", v)
            return
        }
        fmt.Println("key not found:", k)
    }

    k := favContextKey("language")
    ctx := context.WithValue(context.Background(), k, "Go")

    f(ctx, k)
    f(ctx, favContextKey("color"))
}

输入:

found value: Go
key not found: color

本文的大部分内容,包含代码示例都是翻译自官网文档,代码都是通过验证能够执行的。如果有不是特地清晰的中央,能够间接去读官网文档。

以上就是本文的全部内容,如果感觉还不错的话欢送 点赞 转发 关注,感激反对。


官网文档:

  • https://pkg.go.dev/context@go1.20.5

源码剖析:

  • https://mritd.com/2021/06/27/golang-context-source-code/
  • https://www.qtmuniao.com/2020/07/12/go-context/
  • https://seekload.net/2021/11/28/go-context.html

举荐浏览:

  • Go 语言 map 如何程序读取?
  • Go 语言 map 是并发平安的吗?
  • Go 语言切片是如何扩容的?
  • Go 语言数组和切片的区别
  • Go 语言 new 和 make 关键字的区别
  • 为什么 Go 不反对 []T 转换为 []interface
  • 为什么 Go 语言 struct 要应用 tags
退出移动版