2 context.Context引入
//上下文携带截止日期、取消信号和请求范围的值在API的界限。它的方法是安全的同时使用多个了goroutine。
type Context interface {
// Done返回一个在上下文被取消或超时时关闭的通道。
Done() <-chan struct{}
// Err表示在Done通道关闭后为何取消此上下文。
Err() error
// Deadline返回上下文将被取消的时间(如果有的话)。
Deadline() (deadline time.Time, ok bool)
// Value返回与key相关的值,如果没有则返回nil。
Value(key interface{}) interface{}
}
-
该
Done
方法返回一个通道,该通道作为代表运行的函数的取消信号Context
:当通道关闭时,函数应该放弃它们的工作并返回。 -
该
Err
方法返回一个错误,指示Context
取消的原因。 -
一个
Context
对于多个 goroutine 同时使用是安全的。代码可以将单个传递Context
给任意数量的 goroutines 并取消它Context
以向所有goroutine 发出信号。 -
该
Deadline
方法允许函数确定它们是否应该开始工作,还可以使用截止日期来设置 I/O 操作的超时时间。 -
Value
允许一个Context
携带请求范围的数据。该数据必须是安全的,以便多个 goroutine 同时使用。
3 context包的其他常用函数
3.1 context.Background和context.TODO
Background是任何Context树的根,它永远不会被取消:
//Background返回一个空的Context。 它永远不会取消,没有截止日期,没有价值。 Background通常用于main、init和tests,并作为传入请求的顶级上下文。
func Background() Context
给一个函数方法传递Context的时候,不要传递nil,如果不知道传递什么,就使用context.TODO()
3.2 context.WithCancel和
WithCancelt返回派生的Context值,可以比父Context更快地取消。当请求处理程序返回时,通常会取消与传入请求关联的content。当使用多个副本时,WithCancel对于取消冗余请求也很有用。
// WithCancel返回一个父进程的副本,该父进程的Done通道被尽快关闭。 关闭Done或调用cancel。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
// CancelFunc取消一个上下文。
type CancelFunc func()
示例:
package main
import (
"context"
"fmt"
)
func play(ctx context.Context) <-chan int {
dist := make(chan int)
n := 1
//匿名函数 向dist中加入元素
go func() {
for {
select {
//ctx为空时将不会执行这个
case <-ctx.Done():
return // return结束该goroutine,防止泄露
//向dist中加入元素
case dist <- n:
n++
}
}
}()
return dist
}
func main() {
//返回空的context
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 调用cancel
for n := range play(ctx) {
fmt.Println(n)
if n == 5 {
break
}
}
}
扩展:go中select的用法
select的用法与switch语言非常类似,由select开始一个新的选择块,每个选择条件由case语句来描述。
与switch语句相比, select有比较多的限制,其中最大的一条限制就是每个case语句里必须是一个IO操作,大致的结构如下:
```go
select {
case <-chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}
```
在一个select语句中,Go语言会按顺序从头至尾评估每一个发送和接收的语句。
如果其中的任意一语句可以继续执行(即没有被阻塞),那么就从那些可以执行的语句中任意选择一条来使用。
如果没有任意一条语句可以执行(即所有的通道都被阻塞),那么有两种可能的情况:
- 如果给出了default语句,那么就会执行default语句,同时程序的执行会从select语句后的语句中恢复。
- 如果没有default语句,那么select语句将被阻塞,直到至少有一个通信可以进行下去。
3.3 context.WithTimeout
WithTimeout返回派生的Context值,WithTimeout用于设置请求到后端服务器的截止日期:
//WithTimeout返回一个父进程的副本,该父进程的Done通道被立即关闭的父母。关闭“完成”、调用“取消”或超时结束。新
//Context的Deadline是现在的更快+timeout和父的Deadline,如果任何。 如果计时器仍然在运行,则cancel函数释放它资源。
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
// CancelFunc取消一个上下文。
type CancelFunc func()
示例:
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func worker(ctx context.Context) {
LOOP:
for {
fmt.Println("db connecting ...")
time.Sleep(time.Millisecond * 10) // 假设正常连接数据库耗时10毫秒
select {
case <-ctx.Done(): // 50毫秒后自动调用
break LOOP
default:
}
}
fmt.Println("worker done!")
wg.Done()
}
func main() {
// 设置一个50毫秒的超时
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
wg.Add(1)
go worker(ctx)
time.Sleep(time.Second * 5)
cancel() // 通知子goroutine结束
wg.Wait()
fmt.Println("over")
}
执行结果:
3.4 context.WithDeadline
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// 目前的期限已经比新的期限提前
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
propagateCancel(parent, c)
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded) // 截止日期已经过去了
return c, func() { c.cancel(false, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
示例:
package main
import (
"context"
"fmt"
"time"
)
func main() {
d := time.Now().Add(500 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), d)
// 尽管ctx会过期,但在任何情况下调用它的cancel函数都是很好的实践。
// 如果不这样做,可能会使上下文及其父类存活的时间超过必要的时间。
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("over")
case <-ctx.Done():
fmt.Println(ctx.Err())
}
}
执行结果:
3.5 context.WithValue
WithValue提供了一种将请求范围的值与Context关联的方法 :
//WithValue返回父元素的副本,其Value方法返回val for key。
func WithValue(parent Context, key interface{}, val interface{}) Context
了解如何使用context包的最好方法是通过一个已工作的示例。
示例:
package main
import (
"context"
"fmt"
"sync"
"time"
)
type TraceCode string
var wg sync.WaitGroup
func worker(ctx context.Context) {
key := TraceCode("KEY_CODE")
traceCode, ok := ctx.Value(key).(string) // 在子goroutine中获取trace code
if !ok {
fmt.Println("invalid trace code")
}
LOOP:
for {
fmt.Printf("worker,code:%s\n", traceCode)
time.Sleep(time.Millisecond * 10) // 假设正常连接数据库耗时10毫秒
select {
case <-ctx.Done(): // 50毫秒后自动调用
break LOOP
default:
}
}
fmt.Println("worker is over!")
wg.Done()
}
func main() {
// 设置一个50毫秒的超时
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
// 在系统的入口中设置trace code传递给后续启动的goroutine实现日志数据聚合
ctx = context.WithValue(ctx, TraceCode("KEY_CODE"), "12512312234")
wg.Add(1)
go worker(ctx)
time.Sleep(time.Second * 5)
cancel() // 通知子goroutine结束
wg.Wait()
fmt.Println("over")
}
执行结果: