1. Context详解
在 Go 语言中 context 包允许传递一个 “context” 到程序中。 Context 如超时或截止日期(deadline)或通道,来指示停止运行和返回。例如,如果正在执行一个 web 请求或运行一个系统命令,定义一个超时对生产级系统通常是个好主意。因为,如果依赖的API运行缓慢,不希望在系统上备份(back up)请求,因为它可能最终会增加负载并降低所有请求的执行效率。导致级联效应。这是超时或截止日期 context 派上用场的地方。
1.1. 设计原理
Go 语言中的每一个请求的都是通过一个单独的 Goroutine 进行处理的,HTTP/RPC 请求的处理器往往都会启动新的 Goroutine 访问数据库和 RPC 服务,我们可能会创建多个 Goroutine 来处理一次请求,而 Context 的主要作用就是在不同的 Goroutine 之间同步请求特定的数据、取消信号以及处理请求的截止日期。
每一个 Context 都会从最顶层的 Goroutine 一层一层传递到最下层,这也是 Golang 中上下文最常见的使用方式,如果没有 Context,当上层执行的操作出现错误时,下层其实不会收到错误而是会继续执行下去:
当最上层的 Goroutine 因为某些原因执行失败时,下两层的 Goroutine 由于没有接收到这个信号所以会继续工作;但是当我们正确地使用 Context 时,就可以在下层及时停掉无用的工作减少额外资源的消耗:
1.2. Context接口
context.Context 是 Go 语言在 1.7 版本中引入标准库的接口1,该接口定义了四个需要实现的方法,其中包括:
Deadline — 返回 context.Context 被取消的时间,也就是完成工作的截止日期;
Done — 返回一个 Channel,这个 Channel 会在当前工作完成或者上下文被取消之后关闭,多次调用 Done 方法会返回同一个 Channel;
Err — 返回 context.Context 结束的原因,它只会在 Done 返回的 Channel 被关闭时才会返回非空的值;如果 context.Context 被取消,会返回 Canceled 错误;如果 context.Context 超时,会返回 DeadlineExceeded 错误;
Value — 从 context.Context 中获取键对应的值,对于同一个上下文来说,多次调用 Value 并传入相同的 Key 会返回相同的结果,该方法可以用来传递请求特定的数据;
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
}
2. 实例
2.1.取消上下文的控制context.WithCancel
go代码如下,控制一个吃汉堡函数调用的结束:
结束调试是个数》=10的时候
控制函数调用的结束
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
eatNum := chiHanBao(ctx)
for n := range eatNum {
if n>=10{
cancel()
break
}
fmt.Print("n,", n)
time.Sleep(1 * time.Second)
}
fmt.Println("正在统计结果。。。")
time.Sleep(1 * time.Second)
}
func chiHanBao(ctx context.Context) <-chan int {
c := make(chan int)
// 个数
n := 0
// 时间
t := 0
go func() {
for {
select {
case <-ctx.Done():
fmt.Printf("耗时 %d 秒,吃了 %d 个汉堡 \n", t, n)
return
case c <- n:
incr := 1
n += incr
t++
fmt.Printf("我吃了 %d 个汉堡\n", n)
}
}
}()
return c
}
输出:
n,0我吃了 1 个汉堡
n,1我吃了 2 个汉堡
n,2我吃了 3 个汉堡
n,3我吃了 4 个汉堡
n,4我吃了 5 个汉堡
n,5我吃了 6 个汉堡
n,6我吃了 7 个汉堡
n,7我吃了 8 个汉堡
n,8我吃了 9 个汉堡
n,9我吃了 10 个汉堡
正在统计结果。。。
我吃了 11 个汉堡
耗时 11 秒,吃了 11 个汉堡
Exiting.
2.2.超时上下文的控制context.WithTimeout
go代码如下,控制一个吃汉堡函数调用的结束:
控制吃汉堡结束时机:10秒以后
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
chiHanBao2(ctx)
defer cancel()
}
func chiHanBao2(ctx context.Context) {
n := 0
for {
select {
case <-ctx.Done():
fmt.Println("stop \n")
return
default:
n += 1
fmt.Printf("我吃了 %d 个汉堡\n", n)
}
time.Sleep(time.Second)
}
}
效果:
我吃了 1 个汉堡
我吃了 2 个汉堡
我吃了 3 个汉堡
我吃了 4 个汉堡
我吃了 5 个汉堡
我吃了 6 个汉堡
我吃了 7 个汉堡
我吃了 8 个汉堡
我吃了 9 个汉堡
我吃了 10 个汉堡
stop
2.3.期限结束上下文的控制context.WithDeadline
go代码如下,控制一个吃汉堡函数调用的结束:
停止吃汉堡的期限在10秒后
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
chiHanBao2(ctx)
defer cancel()
}
func chiHanBao2(ctx context.Context) {
n := 0
for {
select {
case <-ctx.Done():
fmt.Println("stop \n")
return
default:
incr := 1
n += incr
fmt.Printf("我吃了 %d 个汉堡\n", n)
}
time.Sleep(time.Second)
}
}
我吃了 1 个汉堡
我吃了 2 个汉堡
我吃了 3 个汉堡
我吃了 4 个汉堡
我吃了 5 个汉堡
我吃了 6 个汉堡
我吃了 7 个汉堡
我吃了 8 个汉堡
我吃了 9 个汉堡
我吃了 10 个汉堡
stop
2.4.上下文值的传递
通过上下文值的传递,可以捕捉整个调用栈:
函数间通过context传递值
package main
import (
"context"
"fmt"
)
func main() {
ctx := context.WithValue(context.Background(), "trace_id", "88888888")
// 携带session到后面的程序中去
ctx = context.WithValue(ctx, "session", 1)
fmt.Print("ctx, string", ctx)
process(ctx)
}
func process(ctx context.Context) {
session, ok := ctx.Value("session").(int)
fmt.Println(ok)
if !ok {
fmt.Println("something wrong")
return
}
if session != 1 {
fmt.Println("session 未通过")
return
}
traceID := ctx.Value("trace_id").(string)
fmt.Println("traceID:", traceID, "-session:", session)
}
效果:
traceID: 88888888 -session: 1
标签:context,ctx,golang,Done,Context,time,汉堡,上下文 From: https://www.cnblogs.com/zhanchenjin/p/17090165.html