1,context作用
1,通过context,我们可以方便地对同一个请求所产生地goroutine进行约束管理,可以设定超时、deadline,甚至是取消这个请求相关的所有goroutine。形象地说,假如一个请求过来,需要A去做事情,而A让B去做一些事情,B让C去做一些事情,A、B、C是三个有关联的goroutine,那么问题来了:假如在A、B、C还在处理事情的时候请求被取消了,那么该如何优雅地同时关闭goroutine A、B、C呢?这个时候就轮到context包上场了。
2,在golang中的创建一个新的线程并不会返回像c语言类似的pid所有我们不能从外部杀死某个线程,所有我就得让它自己结束之前我们用channel+select的方式,来解决这个问题但是有些场景实现起来比较麻烦,例如由一个请求衍生出多个线程并且之间需要满足一定的约束关系,以实现一些诸如:
有效期,中止线程树,传递请求全局变量之类的功能。
于是google 就为我们提供一个解决方案,开源了context包。使用context包来实现上下文功能 …..
约定:需要在你的方法的传入参数的第一个参数是context.Context的变量。
2,context结构
使用例子(1):
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.
// 定义 goroutine 函数
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() // main 方法执行完后,结束 ctx 相关的 goroutine
for n := range gen(ctx) {
fmt.Println(n)
if n == 5 {
break
}
}
}
使用例子(2):
package main
import (
"fmt"
"time"
"golang.org/x/net/context"
)
func Cdd(ctx context.Context) int {
fmt.Println(ctx.Value("NLJB"))
select {
// 结束时候做点什么 ...
case <-ctx.Done():
return -3
default:
// 没有结束 ... 执行 ...
}
}
func Bdd(ctx context.Context) int {
fmt.Println(ctx.Value("HELLO"))
fmt.Println(ctx.Value("WROLD"))
ctx = context.WithValue(ctx, "NLJB", "NULIJIABEI")
go fmt.Println(Cdd(ctx))
select {
// 结束时候做点什么 ...
case <-ctx.Done():
return -2
default:
// 没有结束 ... 执行 ...
}
}
func Add(ctx context.Context) int {
ctx = context.WithValue(ctx, "HELLO", "WROLD")
ctx = context.WithValue(ctx, "WROLD", "HELLO")
go fmt.Println(Bdd(ctx))
select {
// 结束时候做点什么 ...
case <-ctx.Done():
return -1
default:
// 没有结束 ... 执行 ...
}
}
func main() {
// 自动取消(定时取消)
{
timeout := 3 * time.Second
ctx, _ := context.WithTimeout(context.Background(), timeout)
fmt.Println(Add(ctx))
}
// 手动取消
// {
// ctx, cancel := context.WithCancel(context.Background())
// go func() {
// time.Sleep(2 * time.Second)
// cancel() // 在调用处主动取消
// }()
// fmt.Println(Add(ctx))
// }
select {}
}
(1)context.Context 接口
- context包里的方法是线程安全的,可以被多个线程使用
- 当Context被canceled或是timeout, Done返回一个被closed 的channel
- 在Done的channel被closed后, Err代表被关闭的原因
- 如果存在, Deadline 返回Context将要关闭的时间
- 如果存在,Value 返回与 key 相关了的值,不存在返回 nil
// context 包的核心
type Context interface {
Done() <-chan struct{}
Err() error
Deadline() (deadline time.Time, ok bool)
Value(key interface{}) interface{}
}
Deadline会返回一个超时时间,Goroutine获得了超时时间后,例如可以对某些io操作设定超时时间。
Done方法返回一个信道(channel)。当Context被cancenl或timeout时,该信道被关闭。它是一个表示Context是否已关闭的信号。
当Done信道关闭后,Err方法表明Context被撤的原因。
Value可以让Goroutine共享一些数据,当然获得数据是协程安全的。但使用这些数据的时候要注意同步,比如返回了一个map,而这个map的读写则要加锁。
(2)接口的实现
我们不需要手动实现这个接口,context 包已经给我们提供了两个
- Background
- TODO
这两个函数都会返回一个Context的实例只是返回的这两个实例都是空 Context。
/*
TODO返回一个非nil,empty的上下文
在目前还不清楚要使用的上下文或尚不可用时
*/
context.TODO()
/*
Background返回一个非nil,empty的上下文。
这是没有取消,没有值,并且没有期限。
它通常用于由主功能,初始化和测试,并作为输入的顶层上下文
*/
context.Background()
3,context包里的方法
(1)方法的使用
Context接口没有提供方法来设置其值和过期时间,也没有提供方法直接将其自身撤销。也就是说,Context不能改变和撤销其自身。那么该怎么通过Context传递改变后的状态呢?
context包里,主要使用的方法:
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 interface{}, val interface{}) Context
WithCancel 对应的是 cancelCtx ,其中,返回一个 cancelCtx ,同时返回一个 CancelFunc,CancelFunc 是 context 包中定义的一个函数类型:type CancelFunc func()。调用这个 CancelFunc 时,关闭对应的c.done,也就是让他的后代goroutine退出
WithDeadline 和 WithTimeout 对应的是 timerCtx ,WithDeadline 和 WithTimeout 是相似的,WithDeadline 是设置具体的 deadline 时间,到达 deadline 的时候,后代 goroutine 退出,而 WithTimeout 简单粗暴,直接 return WithDeadline(parent, time.Now().Add(timeout))
WithValue 对应 valueCtx ,WithValue 是在 Context 中设置一个 map,拿到这个 Context 以及它的后代的 goroutine 都可以拿到 map 里的值
Context 的调用应该是链式的,通过WithCancel,WithDeadline,WithTimeout或WithValue派生出新的 Context。当父 Context 被取消时,其派生的所有 Context 都将取消。
通过context.WithXXX都将返回新的 Context 和 CancelFunc。调用 CancelFunc 将取消子代,移除父代对子代的引用,并且停止所有定时器。未能调用 CancelFunc 将泄漏子代,直到父代被取消或定时器触发。go vet工具检查所有流程控制路径上使用 CancelFuncs。
(2)context的创建
所有的context的父对象,也叫根对象,是一个空的context,它不能被取消,它没有值,从不会被取消,也没有超时时间,它常常作为处理request的顶层context存在,然后通过WithCancel、WithTimeout函数来创建子对象来获得cancel、timeout的能力
当顶层的request请求函数结束后,我们就可以cancel掉某个context,从而通知别的routine结束
其它方法例子
请参考:快速掌握 Golang context 包,简单示例
参考
快速掌握 Golang context 包,简单示例
理解Go Context机制
Golang之Context的使用
golang context初探
未读文章
How to correctly use context.Context in Go 1.7
Context API explained
Go Concurrency Patterns: Context