上下文-Context

Context的作用和意义

Golang这个语言最大的一个优势就是拥有一个高并发利器:goroutine,它是有Golang语言实现的协程,有了它就可以实现高并发请求,但有了大量的协程后,就会带来一些问题,比如:

  • 一些普通参数(例如 LogID、用户 session 等)如何传入协程?
  • 如何可以跟踪goroutine,控制以及终止取消一个协程?
  • 如何做到一个上层任务取消后,所有的下层任务都会被取消?
  • 如何做到中间某一层的任务取消后,只会将当前任务的下层任务取消,而不会影响上层的任务以及同级任务?

Golang中,我们无法从外部终止一个协程,只能它自己结束。常见的比如超时取消等需求,我们通常使用抢占操作或者中断后续操作。

Context 出来以前,Golangchannel + select 的方式来做这件事情的。具体的做法是:定义一个channel,子协程启一个定时任务循环监听这个channel,主协程如果想取消子协程,就往channel里写入信号。

这时我们需要一种优雅的方案来实现这样一种机制,Context就派上用场了。

Context 包的主要作用如下:

  1. 传递请求特定值:在一次请求中,Context可以用于传递请求的特定值,如请求ID、用户信息等等。这些信息可以在整个请求链路中共享,而无需在每个函数调用中显式地传递它们作为参数。
  2. 控制goroutine的取消:Context提供了一种机制来取消一组相关的goroutine,即当父Context被取消时,其所有子Context都会自动取消。这可以避免因长时间的阻塞或等待而导致的资源浪费和应用程序挂起的风险。
  3. 管理请求的截止时间:Context可以用于在请求超时时取消一组相关的goroutine。这可以避免应用程序因等待I/O操作或外部服务响应而被阻塞或挂起。

总之,ContextGo语言中一种非常有用的机制,可以帮助我们在分布式和高并发环境中更轻松地管理上下文信息,以及有效地控制资源的使用和应用程序的响应时间。

Context的实现

Context接口

context.Context是一个接口,接口定义了四个实现的方法:

type Context interface {Deadline() (deadline time.Time, ok bool)Done() <-chan struct{}Err() errorValue(key interface{}) interface{}
}

Context接口包含四个方法:

  • Deadline 返回当前上下文的截止时间(deadline)和一个布尔值,表示是否设置了截止时间。如果截止时间已经过期,则返回过期时间和 true

  • Done 返回一个只读的 channel,该 channel 会在当前上下文被取消或超时时关闭;多次调用 Done 方法会返回同一个 Channel

    • 当绑定当前context的任务被取消时,将返回一个关闭的channel(即 closedchan);

    • 如果当前context不会被取消,将返回nil

    • 其他正常情况,返回一个正常的channel

  • Err 返回当前上下文中的错误信息;

    • 如果Done返回的channel没有关闭,将返回nil;
    • 如果Done返回的channel已经关闭,将返回非空的值表示任务结束的原因
    • 如果是context被取消,Err将返回Canceled
    • 如果是context超时,Err将返回DeadlineExceeded
  • Value 返回与当前上下文关联的键(key)的值。键是一个任意类型的值,可以用来查找与之相关联的值。如果在当前上下文中找不到指定键的值,则返回 nil

默认的Context实现

Context 包中实现了4种默认的 Context实现,基本能满足绝大多数的应用场景。下面简单介绍一下:

emptyCtx

emptyCtx是一个int类型的变量,它是 Context 接口的一个实现。emptyCtx 通常用作上下文树的根节点,在没有其他上下文信息可用的情况下,作为默认的上下文。

emptyCtx 的实现非常简单,它不包含任何额外的字段或数据,只是定义了 Context 接口中的方法,并且这些方法都是空的或默认的实现,如下所示:

type emptyCtx intfunc (*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"
}var (background = new(emptyCtx)todo       = new(emptyCtx)
)func Background() Context {return background
}func TODO() Context {return todo
}

从上述代码中,我们不难发现 emptyCtx通过空方法实现了 context.Context接口中的所有方法,它没有任何功能。

emptyCtx 实例化的两个变量 background todo,分别由两个方法返回:Background() TODO()

一般不会直接使用emptyCtx,而是使用实例化的两个变量,为了正确地处理错误、超时、取消和上下文值等情况,应该尽可能地使用具有明确语义的上下文,例如 context.Background()context.TODO()context.WithTimeout()context.WithCancel()context.WithValue() 等。

以下是一些 emptyCtx 在实际代码中使用的场景:

  1. 在没有传递上下文的情况下,用 emptyCtx 作为默认上下文

    func myFunc() {ctx := context.TODO() // 使用 emptyCtx 作为默认上下文// ...
    }
    
  2. 在单元测试中,当不需要上下文时使用 emptyCtx

    func TestMyFunc(t *testing.T) {ctx := context.TODO() // 在测试中使用 emptyCtx 作为默认上下文result, err := myFunc(ctx)// ...
    }
    
  3. 在没有传递上下文的情况下,使用 emptyCtx 创建新的上下文

    func myFunc() {ctx := context.Background() // 使用 emptyCtx 创建新的上下文// ...
    }
    
  4. 在需要处理错误或日志记录的情况下,使用 emptyCtx 作为基础上下文

    func myFunc() {ctx := context.Background() // 使用 emptyCtx 创建新的上下文// 在上下文中存储错误信息和日志记录ctx = context.WithValue(ctx, "error", nil)ctx = context.WithValue(ctx, "log", "myFunc called")// ...
    }
    

valueCtx

valueCtxContext 接口的一个实现,它通过一个键值对来存储上下文数据。

下面是 valueCtx 的实现代码:

type valueCtx struct {Contextkey, val interface{}
}func (c *valueCtx) Value(key any) any {if c.key == key {return c.val}return value(c.Context, key)
}func WithValue(parent Context, key, val any) Context {if parent == nil {panic("cannot create context from nil parent")}if key == nil {panic("nil key")}if !reflectlite.TypeOf(key).Comparable() {panic("key is not comparable")}return &valueCtx{parent, key, val}
}func value(c Context, key any) any {for {switch ctx := c.(type) {case *valueCtx:if key == ctx.key {return ctx.val}c = ctx.Contextcase *cancelCtx:if key == &cancelCtxKey {return c}c = ctx.Contextcase *timerCtx:if key == &cancelCtxKey {return ctx.cancelCtx}c = ctx.Contextcase *emptyCtx:return nildefault:return c.Value(key)}}
}

valueCtx 结构体包含了一个 Context 类型的成员变量,代表其父级上下文,以及一个键值对 keyval,分别表示上下文数据的键和值。

valueCtx 实现了 Context 接口中的 Value() 方法,用于从上下文中获取数据。如果当前上下文中包含了指定的键值对,那么直接返回该值;否则,将调用父级上下文的 Value() 方法来查找该键值对(如果当前context上不存在需要的key,会沿着context链向上寻找key对应的值,直到根节点)。

另外,WithValue() 函数用于创建一个新的 valueCtx 上下文,将给定的键值对存储在上下文中,并将其父级上下文作为参数传递。如果键值对的键为空或不可比较,则会抛出 panic。这里添加键值对不是在原context结构体上直接添加,而是以此context作为父节点,重新创建一个新的valueCtx子节点,将键值对添加在子节点上,由此形成一条context链。

下面是一个示例,演示如何在上下文中存储和使用一个键值对:

func main() {emptyCtx := context.Background()Context1 := context.WithValue(emptyCtx, "key1", "val1")Context2 := context.WithValue(Context1, "key2", "val2")Context3 := context.WithValue(Context2, "key3", "val3")fmt.Println(Context3.Value("key1"))
}/* 
---------output------------
val1
*/

在这个示例中,我们可以看出如果当前Context上不存在需要的key, 会沿着context链向上寻找key对应的值,直到根节点。该示例的Context链如图:

在这里插入图片描述

除了以上例子,在处理 HTTP 请求时,可以将请求对象存储在上下文中,以便在后续处理过程中可以轻松地访问请求参数、请求头等信息。示例如下:

func HandleRequest(ctx context.Context, w http.ResponseWriter, r *http.Request) {// 将请求对象存储在上下文中ctx = context.WithValue(ctx, "request", r)// 处理请求// ...
}func ProcessData(ctx context.Context) {// 从上下文中获取请求对象r, ok := ctx.Value("request").(*http.Request)if !ok {// 如果请求对象不存在,返回错误return errors.New("request not found")}// 处理数据// ...
}

在这个示例中,我们将 HTTP 请求对象存储在上下文中,并通过 Value() 方法在后续处理过程中访问该请求对象。这使得我们可以在整个请求处理过程中方便地访问请求参数、请求头等信息,而不需要在每个函数中都传递请求对象。

cancelCtx

Context 接口的一个实现,它可以通过取消操作来终止整个请求流程。它的作用是允许我们在请求处理过程中取消某个操作,以及通知其它相关操作停止处理,从而达到快速响应和释放资源的目的。

实现如下:

type cancelCtx struct {// Context 接口,表示当前上下文的父级上下文Context//互斥锁,用于保护以下字段的并发修改mu sync.Mutex//atomic.Value 类型的原子值,用于存储一个 chan struct{} 类型的信道,用于通知取消操作是否完成。该字段会被懒加载,即在第一次调用 WithCancel() 函数创建 cancelCtx 上下文时才会创建并赋值done atomic.Value//用于存储当前上下文创建的子上下文。子上下文可以通过 WithCancel() 或 WithDeadline() 等函数创建,并会在当前上下文被取消时一并取消。该字段会在第一次调用 cancel() 方法时设置为 nil。children map[canceler]struct{}//error 类型的变量,用于存储取消操作的错误信息。该字段会在第一次调用 cancel() 方法时设置为 context.Canceled 或 context.DeadlineExceeded。err error//error 类型的变量,用于存储导致当前上下文被取消的根本原因。该字段会在第一次调用 cancel() 方法时设置为导致当前上下文被取消的根本错误cause error
}type canceler interface {cancel(removeFromParent bool, err, cause error)Done() <-chan struct{}
}

valueCtx类似,cancelCtx 是更加复杂的一个 ctx,它实现了 canceler 接口,支持取消操作,并且取消操作能够往子节点蔓延。

canceler 接口继承了 Context 接口,并在此基础上增加了以下两个方法:

  • cancel(removeFromParent bool, err, cause error) :用于取消当前上下文及其所有子上下文,并设置取消操作的错误信息和根本原因。该方法会接收三个参数:
    • removeFromParent:一个布尔值,表示当前上下文是否应该从其父级上下文中移除。如果为 true,则表示当前上下文是由其父级上下文创建的子上下文,需要从父级上下文中移除;否则为 false,表示当前上下文是一个独立的上下文。
    • err:一个 error 类型的参数,表示取消操作的错误信息。如果该参数为 nil,则表示取消操作是正常完成的;否则表示取消操作是因为发生了错误而被迫中止的。
    • cause:一个 error 类型的参数,表示导致当前上下文被取消的根本原因。
  • Done() <-chan struct{}:返回一个 chan struct{} 类型的信道,该信道会在当前上下文及其所有子上下文被取消时关闭。

通过实现 canceler 接口,一个上下文类型可以支持取消操作,并且可以设置取消操作的错误信息和根本原因。此外,该接口还提供了一个 Done() 方法,用于在当前上下文及其所有子上下文被取消时通知调用方。该方法返回的信道可以用于阻塞当前协程,直到取消操作完成。

cancelCtx实现的方法:

  • Done() Err() String()方法不多说,比较简单易懂,直接看源码

    //这段代码的作用是返回一个 Done channel,以便在 goroutine 中监听该 channel,以了解是否应该取消该 goroutine。
    func (c *cancelCtx) Done() <-chan struct{} {// 尝试读取已经创建的 Done channeld := c.done.Load()if d != nil {return d.(chan struct{})}// 如果没有,则加锁并再次检查c.mu.Lock()defer c.mu.Unlock()d = c.done.Load()// 二次检查,防止竞争if d == nil {// 如果没有,创建 Done channeld = make(chan struct{})c.done.Store(d)}return d.(chan struct{})
    }func (c *cancelCtx) Err() error {c.mu.Lock()err := c.errc.mu.Unlock()return err
    }func (c *cancelCtx) String() string {return contextName(c.Context) + ".WithCancel"
    }
    
  • Value(key interface{})

    var cancelCtxKey int
    func (c *cancelCtx) Value(key interface{}) interface{} {if key == &cancelCtxKey {return c}return c.Context.Value(key)
    }
    

    可以看到,cancelCtx Value方法提供了一个特殊路径,就是如果传入的 key&cancelCtxKey,那么直接返回当前的 ctx。记住这一点,这点非常有趣。

  • cancel(bool, error)

    取消操作,第一个参数代表取消的时候是否要将当前 ctx 从父 ctx 维护的子 ctx 中移除,第二个参数代表要传给 ctxerr(通过 Context.Err() 方法可以捕获)

    func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {// 如果没有传入 cancel 错误,则 panic 报错if err == nil {panic("context: internal error: missing cancel error")}// 如果没有传入 cause 错误,则默认将 err 作为 causeif cause == nil {cause = err}// 加锁,保护以下字段c.mu.Lock()if c.err != nil {c.mu.Unlock()return // 已经取消了}c.err = errc.cause = cause// 获取 Done channel,如果没有,则将 done 置为 closedchand, _ := c.done.Load().(chan struct{})if d == nil {c.done.Store(closedchan)} else {close(d)}// 遍历子节点,并递归地调用 cancel 方法for child := range c.children {child.cancel(false, err, cause)}// 将 children 置为 nil,避免内存泄漏c.children = nilc.mu.Unlock()// 如果 removeFromParent 为 true,则将当前节点从父节点的子节点列表中移除if removeFromParent {removeChild(c.Context, c)}
    }

    cancel 的实现很简单,它会先取消自己(err 赋值,同时关闭 channel),然后将它维护的子节点也给取消掉,最后判断(第一个入参)需不需要将自己从父节点中移除,如果需要的话,就执行 removeChild函数(内部就是调用 delete 内置函数)将自己移除。

    如下图所示,当 cancelCtx1 取消之后,它的子节点 cancelCtx2timerCtx1 以及子节点的子节点 timerCtx2 都会被取消。

    image-20220707144037855

那么如何创建一个可取消的 context 呢?

WithCancel 函数来创建一个可取消的context,即cancelCtx类型的context,源代码如下:

func withCancel(parent Context) *cancelCtx {if parent == nil {panic("cannot create context from nil parent")}c := newCancelCtx(parent)propagateCancel(parent, c)return c
}func newCancelCtx(parent Context) cancelCtx {return cancelCtx{Context: parent}
}
  • 函数先判断传入的 parent 参数是否为 nil,如果为 nil,则会抛出一个 panic 异常。

  • 接着,函数调用 newCancelCtx(parent) 创建了一个新的 cancelCtx 对象 cnewCancelCtx() 函数的作用是,以 parent 作为父上下文,创建一个新的 cancelCtx 对象并返回。新的 cancelCtx 对象会包含一个初始为空的 done 通道和一个空的 children 映射。

  • 然后,函数调用 propagateCancel(parent, c) 将新创建的 cancelCtx 对象 c 注册到其父上下文 parent 中。propagateCancel() 函数的作用是,将新创建的 cancelCtx 对象 c 与其父上下文 parent 关联起来。具体而言,propagateCancel() 函数会将新创建的 cancelCtx 对象 c 添加到其父上下文 parentchildren 映射中。

  • 最后,函数返回新创建的 cancelCtx 对象 c

来看下这里面的propagateCancel函数,该作用是将子 cancelCtx 挂载到父 cancelCtx 上面,这样的话,当父 cancelCtx 被取消之后,它下面挂载的所有子 cancelCtx 都可以被取消。这个方法是实现 cancel 传播的前提。

实现代码如下:

func propagateCancel(parent Context, child canceler) {// 获取 parent 的 done 通道done := parent.Done()// 如果 parent 没有 done 通道,表示 parent 永远不会被取消if done == nil {return // parent is never canceled}// 如果 parent 已经被取消,就直接取消 childselect {case <-done:child.cancel(false, parent.Err(), Cause(parent))returndefault:}// 如果 parent 是一个 cancelCtx,则将 child 添加到 parent 的 children 中if p, ok := parentCancelCtx(parent); ok {p.mu.Lock()if p.err != nil {child.cancel(false, p.err, p.cause)} else {if p.children == nil {p.children = make(map[canceler]struct{})}p.children[child] = struct{}{}}p.mu.Unlock()} else {// 如果 parent 不是 cancelCtx,则启动一个 goroutine 监听 parent 和 child 的取消事件goroutines.Add(1)go func() {select {// 如果 parent 被取消,就取消 childcase <-parent.Done():child.cancel(false, parent.Err(), Cause(parent))// 如果 child 被取消,就不再关心 parent 是否被取消case <-child.Done():}goroutines.Done()}()}
}

函数的作用是将父 Context 的取消操作传递给子 Context,也就是当父 Context 取消时,所有与之相关的子 Context 也会被取消。流程如下:

  1. 获取父 Contextdone channel(通过 Done 方法)。
  2. 如果父 Context 没有 done channel,说明它永远不会被取消,直接返回。
  3. 如果父 Context 已经被取消,将错误原因 err 和原因 cause 传递给子 Context 并立即取消它。
  4. 如果父 Context 没有被取消,将子 Context 添加到父 Contextchildren 集合中,并监听子 Context 的 done channel 和父 Contextdone channel,以便在任何一个 channel 被关闭时及时取消子 Context。在这个过程中,需要加锁保护 children 集合。
  5. 如果父 Context 是一个根 Context,启动一个新的 goroutine 监听父 Context 和子 Context 的 done channel,以便在任意一个 channel 被关闭时及时取消子 Context。需要注意的是,这种情况下需要使用 sync.WaitGroup 等待所有 goroutine 执行完成。

下面是一个简单的示例,演示如何在一个 HTTP 服务器中使用 cancelCtx

package mainimport ("context""fmt""log""net/http""time"
)func main() {mux := http.NewServeMux()mux.HandleFunc("/", handler)srv := &http.Server{Addr:         ":8080",Handler:      mux,ReadTimeout:  5 * time.Second,WriteTimeout: 10 * time.Second,}go func() {<-time.After(15 * time.Second)srv.Shutdown(context.Background())}()log.Println("Server is listening on :8080")if err := srv.ListenAndServe(); err != http.ErrServerClosed {log.Fatalf("Server failed: %v", err)}
}func handler(w http.ResponseWriter, r *http.Request) {ctx, cancel := context.WithCancel(r.Context())defer cancel()go longRunningTask(ctx)select {case <-ctx.Done():fmt.Fprintf(w, "Request canceled\n")case <-time.After(10 * time.Second):fmt.Fprintf(w, "Request completed\n")}
}func longRunningTask(ctx context.Context) {select {case <-time.After(20 * time.Second):fmt.Println("Task completed")case <-ctx.Done():fmt.Println("Task canceled")}
}

在上面的例子中,我们创建了一个带有 cancelCtx 的上下文,并将它作为 HTTP 请求处理函数的子上下文。当客户端请求超时或取消时,我们通过调用 cancel() 函数取消这个上下文。在 handler 函数中,我们使用了一个 select 语句,等待长时间运行的任务 longRunningTask 完成或被取消,然后返回相应的消息给客户端。

运行这个程序,然后在浏览器中访问 http://localhost:8080,我们可以看到请求完成或被取消的消息。当我们在 15 秒之后关闭服务器时,我们可以看到 longRunningTask 被取消的消息。

timerCtx

timerCtx 是一个实现了定时器的 Context 实现,它继承了 cancelCtx,并在其基础上增加了一个 timer 和一个 deadline 字段,用于在指定时间后取消该 Context。当 timerCtx 被创建时,会自动创建一个定时器 timer,并在指定的时间后自动取消该 Context。如果在指定时间之前,该 Context 被取消,则会立即关闭 timer

因此,timerCtx 主要用于实现带有超时机制的操作,例如网络请求、资源请求等。在这些场景中,需要在一定时间内得到结果,否则就认为该操作失败。

其结构体定义如下:

type timerCtx struct {// 维护一个 cancelCtxcancelCtx// 通过 cancelCtx.mu 加锁保护timer *time.Timer// 超时时间deadline time.Time
}

timerCtx内部继承了 cancelCtx 的相关变量和方法,还修改了 cancel方法和增加了 Deadline 方法:

//获取该 timerCtx 实例的截止时间
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {return c.deadline, true
}func (c *timerCtx) cancel(removeFromParent bool, err, cause error) {//调用了 cancelCtx 的 cancel 方法,将其标记为已取消状态c.cancelCtx.cancel(false, err, cause)//如果 removeFromParent 为 true,则将当前 timerCtx 从其父上下文中移除if removeFromParent {removeChild(c.cancelCtx.Context, c)}//函数获取 timerCtx 中的锁,停止计时器并将其置为 nil,最后释放锁c.mu.Lock()if c.timer != nil {c.timer.Stop()c.timer = nil}c.mu.Unlock()
}

timeCtx 提供了WithDeadlineWithTimeout 两种方法来实现了定时器的 Context

  • WithDeadline(Context, time.Time)

    WithDeadline返回一个基于parent的可取消的context,并且其过期时间deadline不晚于所设置时间d

    func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {// 确保 parent 不为 nilif parent == nil {panic("cannot create context from nil parent")}// 如果 parent 的 deadline 已经比 d 更早,则直接使用 WithCancel 创建if cur, ok := parent.Deadline(); ok && cur.Before(d) {return WithCancel(parent)}// 创建一个新的 timerCtx 结构体实例c := &timerCtx{cancelCtx: newCancelCtx(parent),deadline:  d,}// 将取消信号向下传播给子节点propagateCancel(parent, c)// 计算当前时间距离 deadline 的时间差dur := time.Until(d)// 如果已经超过了 deadline,直接设置 ctx 的错误为 DeadlineExceeded 并返回if dur <= 0 {c.cancel(true, DeadlineExceeded, nil)return c, func() { c.cancel(false, Canceled, nil) }}// 在计时器到期时取消 ctxc.mu.Lock()defer c.mu.Unlock()if c.err == nil {c.timer = time.AfterFunc(dur, func() {c.cancel(true, DeadlineExceeded, nil)})}return c, func() { c.cancel(true, Canceled, nil) }
    }

    该函数会创建一个新的 timerCtx 实例,其内嵌了一个 cancelCtx,表示在 WithDeadline 函数创建的这个新的 Context 被取消时,所有的子 Context 都会被取消。同时也会创建一个计时器,在计时器到期时自动取消 Context。

    • 如果 parent 的 Deadline 已经比 d 更早,那么直接使用 WithCancel 创建一个新的 Context。

    • 如果已经超过了 d,则直接将 Context 的错误设置为 DeadlineExceeded 并返回。

    • 否则,计算当前时间距离 deadline 的时间差 dur,并创建计时器。当计时器到期时,将 Context 取消,并设置错误为 DeadlineExceeded。同时也提供了一个 CancelFunc 函数,用于取消 Context。

  • WithTimeout(Context, time.Duration)

    WithDeadline类似,WithTimeout也是创建一个定时取消的context,只不过WithDeadline是接收一个过期时间点,而WithTimeout接收一个相对当前时间的过期时长timeout:

    func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {return WithDeadline(parent, time.Now().Add(timeout))
    }
    

下面是一个示例演示,使用 timerCtx 实现了一个简单的网络请求,如果请求时间超过 3 秒,就自动取消该请求:

package mainimport ("context""fmt""io/ioutil""net/http""time"
)func main() {ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(3*time.Second))defer cancel()req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://test.com.cn/1", nil)if err != nil {fmt.Println("Error creating request:", err)return}client := &http.Client{}resp, err := client.Do(req)if err != nil {fmt.Println("Error sending request:", err)return}defer resp.Body.Close()body, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println("Error reading response body:", err)return}fmt.Println("Response body:", string(body))
}

在这个示例中,我们使用 context.WithDeadline 创建了一个带有截止时间的 context.Context 对象,并传递给 http.NewRequestWithContext 方法,使得该请求受到该上下文的约束。如果请求时间超过 3 秒,则 timerCtx 会自动取消该请求。

Context使用建议

在官方博客里,对于使用 context 提出了几点建议:

  • 不要将 Context 塞到结构体里。直接将 Context 类型作为函数的第一参数,而且一般都命名为 ctx
  • 不要向函数传入一个 nilcontext,如果你实在不知道传什么,标准库给你准备好了一个 context:todo
  • 不要把本应该作为函数参数的类型塞到 context 中,context 存储的应该是一些共同的数据。例如:登陆的 session、cookie 等。
  • 同一个 context 可能会被传递到多个 goroutine,别担心,context 是并发安全的。

参考资料:

ChatGPT https://chat.openai.com/

Go 语言设计与实现 https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-context/

Go进阶训练营 https://lailin.xyz/post/go-training-week3-context.html

simanstar https://blog.csdn.net/simanstar/article/details/121313233

李木子啊 https://www.modb.pro/db/129836

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/9793.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【HelloKitty团队项目】Beta阶段项目展示

项目内容这个作业属于哪个课程2023北航软件工程这个作业的要求在哪里团队项目-Beta阶段项目展示我在这个课程的目标是学习软件工程技术&#xff0c;完成团队开发流程这个作业在哪个具体方面帮助我实现目标Beta阶段开发 &#xff08;一&#xff09;项目亮点 一、项目管理 团队…

对垃圾收集器一脸懵B,看这篇就对了|金三银四系列

本文详解了7种垃圾收集器&#xff0c;文章很干&#xff0c;适合用来面试前复习。建议收藏再看&#xff01; 点击上方“后端开发技术”&#xff0c;选择“设为星标” &#xff0c;优质资源及时送达 上一篇文章讲了垃圾回收算法&#xff0c;它是内存回收的方法论&#xff0c;垃圾…

【HelloKitty团队项目】Alpha阶段项目展示

项目内容这个作业属于哪个课程2023北航软件工程这个作业的要求在哪里团队项目-Alpha阶段项目展示我在这个课程的目标是学习软件工程技术&#xff0c;完成团队开发流程这个作业在哪个具体方面帮助我实现目标Alpha阶段开发 一、项目亮点 项目管理 团队是如何进行项目管理的&am…

springboot使用aop切面统一处理查询不同接口中接收的请求体,实现多条件分页查询

目录 需求描述 前端ajax请求调用查询接口示例 准备工作 引入相关依赖 实体类 controller层 service层接口 service层实现类 mapper层 mapper.xml中的selectAll复杂动态sql 控制层切面 工具类MyUtils 通用类DataVO 发送请求查看响应结果 ajax请求体 响应内容 关…

chatgpt赋能python:Python如何阻止弹窗

Python如何阻止弹窗 Python是一种高级编程语言&#xff0c;它具有广泛的应用和丰富的库。它还可以被用于开发自动化程序&#xff0c;包括阻止弹窗。在本文中&#xff0c;我们将介绍如何使用Python阻止弹出窗口&#xff0c;并探讨防止弹窗的原因。 为什么要防止弹窗&#xff1…

MySQL - 各种超时时间 - 学习与探究

1.应用场景 主要用于学习与探究MySQL各种超时时间&#xff0c;应用在合适的场景下. 2.学习/操作 1.文档阅读 https://wen.geekr.dev/ chatgpt & 官方文档 & 其他资料 2.整理输出 2.1 是什么 MySQL中有多个超时时间&#xff0c;以下是其中的几个&#xff1a; connect_…

大语言模型 AI 辅助编码使用过程体验报告(Github Copilot、Cursor)

编码感受和评估 在过去一周多的时间里&#xff0c;我在 ChatGPT 的协助下&#xff0c;生成了做一个简单编辑器的产品文档、技术方案文档&#xff0c;然后在这个基础上&#xff0c;进行程序的编码。 使用的工具纪要 为了更全面地感受 AI IDE 对研发过程的影响&#xff0c;我特…

产品设计师使用ChatGPT的十大妙招

掌握ChatGPT 提示列表,将大大提高产品设计师的效率。 微信搜索关注《Java学研大本营》&#xff0c;加入读者群&#xff0c;分享更多精彩 OpenAI 的 ChatGPT 无处不在&#xff0c;人们将其用于各种各样的事情&#xff0c;从作业作弊到构建产品。最近开始使用 ChatGPT 来摆脱空白…

postman接口报文返回:系统异常

场景&#xff1a;在做python脚本参数化时&#xff0c;同样的请求报文&#xff0c;在postman里可以发送成功&#xff0c;但是发给同事做自动化跑接口时&#xff0c;却返回"系统异常"。 今天在做Jmeter时&#xff0c;发现又出现了同样的问题。 原因&#xff1a;因为没…

postman能请求到后端接口,.HttpMessageNotReadableException: I/O error while reading input message; nested exce

postman能请求到后端接口,.但是前端发送请求&#xff0c;怎么请求&#xff0c;后端接口都没响应.... 前端项目是vue-element-admin 报错信息&#xff1a; HttpMessageNotReadableException: I/O error while reading input message; nested exception is org.apache.catalina…

关于POST发送数据过大,发送请求发生错误问题的原因及办法。

问题来自于生产的一个批量处理提交操作&#xff0c;当POST请求提交的数据量过大时&#xff0c;就会产生错误&#xff0c;发生例如&#xff1a;超时、504等等现象。惊讶之余&#xff0c;并不着急解决BUG&#xff0c;更想弄清楚为什么POST请求会出现这种情况&#xff0c;第一反应…

使用postman发送post请求,却报错不支持get请求的原因

场景复现 可以看到我们postman发出的确实是post请求&#xff0c;message却报错这个接口不支持get请求&#xff0c;说明服务器实际上收到的是一个get请求。 产生原因分析 如果我们访问的是线上的接口&#xff0c;线上的nginx一般都会对http访问做一个302重定向&#xff0c;跳转…

postman,浏览器测试接口正常,HttpClient 调用就报错

一次奔溃的经历 事情是这样的&#xff1a;第三方提供了一个接口需要对接&#xff0c;我就对接了&#xff0c;测试环节的时候怎么都调不通&#xff0c;各种排查&#xff0c;各方人员都动员了起来&#xff0c;就是没有找到问题&#xff0c;下面把问题报错的原因呈上&#xff1a; …

基于Sanic(Python)和ChatGPT的超级简易聊天应用

文章目录 一、项目简介&#xff1a;二、代码结构&#xff1a;三、具体代码&#xff1a;main.pytemplates/index.htmlstatic/css/custom.cssstatic/js/custom.js 四、使用方法:1. 安装依赖&#xff1a;2. 在main.py中替换自己的openai_key3. 运行项目&#xff1a;4. 打开浏览器&…

如何评价OpenAI的超级对话模型ChatGPT?

Trinkle回答&#xff1a; 有幸参与ChatGPT训练的全过程。直接上想法&#xff1a; RLHF会改变现在的research现状&#xff0c;个人认为一些很promising的方向&#xff1a;在LM上重新走一遍RL的路&#xff1b;如何更高效去训练RM和RL policy&#xff1b;写一个highly optimized R…

2019美研计算机录取,2019美研录取更新 | 春节OFFER大集锦,没有比OFFER更好的新年礼物了!...

原标题&#xff1a;2019美研录取更新 | 春节OFFER大集锦&#xff0c;没有比OFFER更好的新年礼物了! 嗨&#xff0c;春节有比收到红包更让人激动的事儿吗&#xff1f; 有&#xff01;比如收到OFFER&#xff01; 继二月初惊喜地收获两枚斯坦福大学的硕士录取后&#xff0c; 过去的…

美研计算机案例,[04.23]公开课丨美研计算机专业分享,让你进军米国IT届

【4.23公开课】美研计算机专业分享&#xff0c;让你进军米国IT届 活动类型:线上活动 开始时间:2015-4-23 20:00 活动地点:天道公开课交流群274304450 性别要求:不限 如今申请赴美读研的人越来越多&#xff0c;一方面美国著名大学的研究生院占据了世界高校专业排名前列的半壁江山…

2019美研计算机录取,2019美研录取更新 | 哥伦比亚大学、芝加哥大学OFFER携手来袭...

原标题&#xff1a;2019美研录取更新 | 哥伦比亚大学、芝加哥大学OFFER携手来袭 福 临近春节 集五福活动又双叒叕来了 福气满满的日子里 连OFFER雨都密了起来!!!!!!!! 今天第一个要恭喜的是来自上海交通大学的L同学 跨学科申请收获哥伦比亚大学公共卫生专业及芝加哥大学生物医学…

2019美研计算机录取,2019美研录取更新 | 又到周五,是时候晒OFFER了!

原标题&#xff1a;2019美研录取更新 | 又到周五&#xff0c;是时候晒OFFER了&#xff01; 又到周五&#xff01; 捂了几天的OFFER们 是时候挑一些拿出来晒晒了。 【今日OFFER雨重点预告】 宾夕法尼亚大学博士全奖录取一枚 佐治亚理工学院博士全奖录取一枚 约翰霍普金斯大学硕士…

商汤版ChatGPT「商量」来了,开放API!

国产ChatGPT之战&#xff0c;已然是大步迈进白热化阶段。 就在刚刚&#xff0c;商汤正式发布自研类ChatGPT产品&#xff0c;名曰商量&#xff08;SenseChat&#xff09;。 单单是这个名字&#xff0c;便值得说道一番。 商量的“商”&#xff0c;不仅体现了它是商汤自家“商字辈…