你好,我是四哥。stQ28资讯网——每日最新资讯28at.com
前一篇文章从源码的角度详细介绍了 Context 的实现原理,但是还没有提到 Context 的使用场景,今天我们一起来看下:stQ28资讯网——每日最新资讯28at.com
1.请求链路传值。stQ28资讯网——每日最新资讯28at.com
传值使用方式如下:stQ28资讯网——每日最新资讯28at.com
func func1(ctx context.Context) {stQ28资讯网——每日最新资讯28at.com
ctx = context.WithValue(ctx, "k1", "v1")stQ28资讯网——每日最新资讯28at.com
func2(ctx)stQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
stQ28资讯网——每日最新资讯28at.com
func func2(ctx context.Context) {stQ28资讯网——每日最新资讯28at.com
fmt.Println("func2:",ctx.Value("k1").(string))stQ28资讯网——每日最新资讯28at.com
ctx = context.WithValue(ctx, "k2", "v2")stQ28资讯网——每日最新资讯28at.com
func3(ctx)stQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
stQ28资讯网——每日最新资讯28at.com
func func3(ctx context.Context) {stQ28资讯网——每日最新资讯28at.com
fmt.Println("func3:",ctx.Value("k1").(string))stQ28资讯网——每日最新资讯28at.com
fmt.Println("func3:",ctx.Value("k2").(string))stQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
stQ28资讯网——每日最新资讯28at.com
func main() {stQ28资讯网——每日最新资讯28at.com
ctx := context.Background()stQ28资讯网——每日最新资讯28at.com
func1(ctx)stQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
我们在 func1() 通过函数 WithValue() 设置了一个键值对 k1-v1,在 func2() 可以获取到 func1() 设置的键值对,如果调用 func3() 时把这个 ctx 继续传入的话,在 func3() 中依然还是可以获取到 k1-v1。stQ28资讯网——每日最新资讯28at.com
但是在 func1() 中获取不到 func2() 设置的键值对 k2-v2,因为 context 只能自上而下携带值,这点需要注意。stQ28资讯网——每日最新资讯28at.com
2.取消耗时操作,及时释放资源。stQ28资讯网——每日最新资讯28at.com
使用 channel + select 的机制:stQ28资讯网——每日最新资讯28at.com
func func1() error {stQ28资讯网——每日最新资讯28at.com
respC := make(chan int) // 起消息通知作用stQ28资讯网——每日最新资讯28at.com
// 处理逻辑stQ28资讯网——每日最新资讯28at.com
go func() {stQ28资讯网——每日最新资讯28at.com
time.Sleep(time.Second * 3) // 模拟处理业务逻辑stQ28资讯网——每日最新资讯28at.com
respC close(respC)stQ28资讯网——每日最新资讯28at.com
}()stQ28资讯网——每日最新资讯28at.com
stQ28资讯网——每日最新资讯28at.com
// 判断是否超时stQ28资讯网——每日最新资讯28at.com
select {stQ28资讯网——每日最新资讯28at.com
case r := <-respC:stQ28资讯网——每日最新资讯28at.com
fmt.Printf("Resp: %d ", r)stQ28资讯网——每日最新资讯28at.com
return nilstQ28资讯网——每日最新资讯28at.com
case <-time.After(time.Second * 2): // 超过设置的时间就报错stQ28资讯网——每日最新资讯28at.com
fmt.Println("catch timeout")stQ28资讯网——每日最新资讯28at.com
return errors.New("timeout")stQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
stQ28资讯网——每日最新资讯28at.com
func main() {stQ28资讯网——每日最新资讯28at.com
err := func1()stQ28资讯网——每日最新资讯28at.com
fmt.Printf("func1 error: %v ", err)stQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
上面的方式平时也会用到,通过 context 怎么实现呢?stQ28资讯网——每日最新资讯28at.com
下面来看下如何使用 context 进行主动取消、超时取消。stQ28资讯网——每日最新资讯28at.com
主动取消:stQ28资讯网——每日最新资讯28at.com
func func1(ctx context.Context, wg *sync.WaitGroup) error {stQ28资讯网——每日最新资讯28at.com
defer wg.Done()stQ28资讯网——每日最新资讯28at.com
respC := make(chan int)stQ28资讯网——每日最新资讯28at.com
go func() {stQ28资讯网——每日最新资讯28at.com
time.Sleep(time.Second * 5) // 模拟业务逻辑处理stQ28资讯网——每日最新资讯28at.com
respC }()stQ28资讯网——每日最新资讯28at.com
// 取消机制stQ28资讯网——每日最新资讯28at.com
select {stQ28资讯网——每日最新资讯28at.com
case <-ctx.Done():stQ28资讯网——每日最新资讯28at.com
fmt.Println("cancel")stQ28资讯网——每日最新资讯28at.com
return errors.New("cancel")stQ28资讯网——每日最新资讯28at.com
case r := <-respC:stQ28资讯网——每日最新资讯28at.com
fmt.Println(r)stQ28资讯网——每日最新资讯28at.com
return nilstQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
stQ28资讯网——每日最新资讯28at.com
func main() {stQ28资讯网——每日最新资讯28at.com
wg := &sync.WaitGroup{}stQ28资讯网——每日最新资讯28at.com
ctx, cancel := context.WithCancel(context.Background())stQ28资讯网——每日最新资讯28at.com
wg.Add(1)stQ28资讯网——每日最新资讯28at.com
go func1(ctx, wg)stQ28资讯网——每日最新资讯28at.com
time.Sleep(time.Second * 2)stQ28资讯网——每日最新资讯28at.com
cancel() // 主动取消stQ28资讯网——每日最新资讯28at.com
wg.Wait() // 等待 goroutine 退出stQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
超时取消:stQ28资讯网——每日最新资讯28at.com
func func1(ctx context.Context) {stQ28资讯网——每日最新资讯28at.com
resp := make(chan int)stQ28资讯网——每日最新资讯28at.com
go func() {stQ28资讯网——每日最新资讯28at.com
time.Sleep(time.Second * 5) // 模拟处理逻辑stQ28资讯网——每日最新资讯28at.com
resp }()stQ28资讯网——每日最新资讯28at.com
// 超时机制stQ28资讯网——每日最新资讯28at.com
select {stQ28资讯网——每日最新资讯28at.com
case <-ctx.Done():stQ28资讯网——每日最新资讯28at.com
fmt.Println("ctx timeout")stQ28资讯网——每日最新资讯28at.com
fmt.Println(ctx.Err())stQ28资讯网——每日最新资讯28at.com
case <-resp:stQ28资讯网——每日最新资讯28at.com
fmt.Println("done")stQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
returnstQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
func main() {stQ28资讯网——每日最新资讯28at.com
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)stQ28资讯网——每日最新资讯28at.com
defer cancel()stQ28资讯网——每日最新资讯28at.com
func1(ctx)stQ28资讯网——每日最新资讯28at.com
}3.防止 goroutine 泄露。stQ28资讯网——每日最新资讯28at.com
引自【深度解密 Go 语言之 context[1]】stQ28资讯网——每日最新资讯28at.com
func gen() ch := make(chan int)stQ28资讯网——每日最新资讯28at.com
go func() {stQ28资讯网——每日最新资讯28at.com
var n intstQ28资讯网——每日最新资讯28at.com
for {stQ28资讯网——每日最新资讯28at.com
ch n++stQ28资讯网——每日最新资讯28at.com
time.Sleep(time.Second)stQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
}()stQ28资讯网——每日最新资讯28at.com
return chstQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
这是一个可以生成无限整数的协程,但如果我只需要它产生的前 5 个数,那么就会发生 goroutine 泄漏:stQ28资讯网——每日最新资讯28at.com
func main() {stQ28资讯网——每日最新资讯28at.com
for n := range gen() {stQ28资讯网——每日最新资讯28at.com
fmt.Println(n)stQ28资讯网——每日最新资讯28at.com
if n == 5 {stQ28资讯网——每日最新资讯28at.com
breakstQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
// ……stQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
当 n == 5 的时候,直接 break 掉。那么 gen 函数的协程就会执行无限循环,永远不会停下来。发生了 goroutine 泄漏。stQ28资讯网——每日最新资讯28at.com
用 context 改进这个例子:stQ28资讯网——每日最新资讯28at.com
func gen(ctx context.Context) ch := make(chan int)stQ28资讯网——每日最新资讯28at.com
go func() {stQ28资讯网——每日最新资讯28at.com
var n intstQ28资讯网——每日最新资讯28at.com
for {stQ28资讯网——每日最新资讯28at.com
select {stQ28资讯网——每日最新资讯28at.com
case <-ctx.Done():stQ28资讯网——每日最新资讯28at.com
returnstQ28资讯网——每日最新资讯28at.com
case ch n++stQ28资讯网——每日最新资讯28at.com
time.Sleep(time.Second)stQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
}()stQ28资讯网——每日最新资讯28at.com
return chstQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
stQ28资讯网——每日最新资讯28at.com
func main() {stQ28资讯网——每日最新资讯28at.com
ctx, cancel := context.WithCancel(context.Background())stQ28资讯网——每日最新资讯28at.com
defer cancel() // 避免其他地方忘记 cancel,且重复调用不影响stQ28资讯网——每日最新资讯28at.com
stQ28资讯网——每日最新资讯28at.com
for n := range gen(ctx) {stQ28资讯网——每日最新资讯28at.com
fmt.Println(n)stQ28资讯网——每日最新资讯28at.com
if n == 5 {stQ28资讯网——每日最新资讯28at.com
cancel()stQ28资讯网——每日最新资讯28at.com
breakstQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
// ……stQ28资讯网——每日最新资讯28at.com
}stQ28资讯网——每日最新资讯28at.com
增加一个 context,在 break 前调用 cancel 函数,取消 goroutine。gen 函数在接收到取消信号后,直接退出,系统回收资源。stQ28资讯网——每日最新资讯28at.com
总结stQ28资讯网——每日最新资讯28at.com
这篇文章列出的几个例子是 context 最基本的使用场景,其他框架、第三包基本上都是从这几种用法扩展的,所以非常有必要掌握基础用法。stQ28资讯网——每日最新资讯28at.com
另外希望这篇文章能给你带来帮助,如果文中有理解错误之处或者你还想到其他用法,可以在留言区留言,一定回复!抱团学习不孤单!stQ28资讯网——每日最新资讯28at.com
参考资料stQ28资讯网——每日最新资讯28at.com
[1]深度解密Go语言之context: https://qcrao.com/2019/06/12/dive-into-go-context/stQ28资讯网——每日最新资讯28at.com