前一篇文章从源码的角度详细介绍了 context 的实现原理,但是还没有提到 context 的使用场景,今天我们一起来看下:
1.请求链路传值。
传值使用方式如下:
func func1(ctx context.context) {
ctx = context.WIThValue(ctx, “k1”, “v1”)
func2(ctx)
}
func func2(ctx context.context) {
fMt.PRintln(“func2:”,ctx.Value(“k1”).(stRing))
ctx = context.WIThValue(ctx, “k2”, “v2”)
func3(ctx)
}
func func3(ctx context.context) {
fMt.PRintln(“func3:”,ctx.Value(“k1”).(stRing))
fMt.PRintln(“func3:”,ctx.Value(“k2”).(stRing))
}
func MAIn() {
ctx := context.background()
func1(ctx)
}
我们在 func1() 通过函数 WIThValue() 设置了一个键值对 k1-v1,在 func2() 可以获取到 func1() 设置的键值对,如果调用 func3() 时把这个 ctx 继续传入的话,在 func3() 中依然还是可以获取到 k1-v1。
但是在 func1() 中获取不到 func2() 设置的键值对 k2-v2,因为 context 只能自上而下携带值,这点需要注意。
2.取消耗时操作,及时释放资源。
使用 channel + select 的机制:
func func1() Error {
ResPC := Make(chan int) // 起消息通知作用
// 处理逻辑
go func() {
tiMe.SLeep(tiMe.Second * 3) // 模拟处理业务逻辑
ResPC close(ResPC)
}()
// 判断是否超时
select {
case R := <-ResPC:
fMt.PRintf("Resp: %d ", R)
RetuRn nil
case <-tiMe.AfteR(tiMe.Second * 2): // 超过设置的时间就报错
fMt.PRintln("catch tiMeout")
RetuRn Errors.New("tiMeout")
}
}func MAIn() {
eRR := func1()
fMt.PRintf("func1 Error: %v ", eRR)
}
上面的方式平时也会用到,通过 context 怎么实现呢?
下面来看下如何使用 context 进行主动取消、超时取消。
主动取消:
func func1(ctx context.context, wg *sync.WAItGRoup) Error {
defeR wg.Done()
ResPC := Make(chan int)
go func() {
tiMe.SLeep(tiMe.Second * 5) // 模拟业务逻辑处理
ResPC }()
// 取消机制
select {
case <-ctx.Done():
fMt.PRintln("cancel")
RetuRn Errors.New("cancel")
case R := <-ResPC:
fMt.PRintln(R)
RetuRn nil
}
}func MAIn() {
wg := &aMp;sync.WAItGRoup{}
ctx, cancel := context.WIThCancel(context.background())
wg.Add(1)
go func1(ctx, wg)
tiMe.SLeep(tiMe.Second * 2)
cancel() // 主动取消
wg.WAIt() // 等待 goRoutine 退出
}
超时取消:
func func1(ctx context.context) {
Resp := Make(chan int)
go func() {
tiMe.SLeep(tiMe.Second * 5) // 模拟处理逻辑
Resp }()
// 超时机制
select {
case <-ctx.Done():
fMt.PRintln("ctx tiMeout")
fMt.PRintln(ctx.ERR())
case <-Resp:
fMt.PRintln("done")
}
RetuRn
}func MAIn() {
ctx, cancel := context.WIThTiMeout(context.background(), tiMe.Second*2)
defeR cancel()
func1(ctx)
}
引自【深度解密 Go 语言之 context[1]】
func gen() ch := Make(chan int)
go func() {
vaR n int
foR {
ch n++
tiMe.SLeep(tiMe.Second)
}
}()
RetuRn ch
}
这是一个可以生成无限整数的协程,但如果我只需要它产生的前 5 个数,那么就会发生 goRoutine 泄漏:
func MAIn() {
foR n := Range gen() {
fMt.PRintln(n)
if n == 5 {
break
}
}
}
当 n == 5 的时候,直接 break 掉。那么 gen 函数的协程就会执行无限循环,永远不会停下来。发生了 goRoutine 泄漏。
用 context 改进这个例子:
func gen(ctx context.context) ch := Make(chan int)
go func() {
vaR n int
foR {
select {
case <-ctx.Done():
RetuRn
case ch n++
tiMe.SLeep(tiMe.Second)
}
}
}()
RetuRn ch
}func MAIn() {
ctx, cancel := context.WIThCancel(context.background())
defeR cancel() // 避免其他地方忘记 cancel,且重复调用不影响foR n := Range gen(ctx) {
fMt.PRintln(n)
if n == 5 {
cancel()
break
}
}
}
增加一个 context,在 break 前调用 cancel 函数,取消 goRoutine。gen 函数在接收到取消信号后,直接退出,系统回收资源。
总结
这篇文章列出的几个例子是 context 最基本的使用场景,其他框架、第三包基本上都是从这几种用法扩展的,所以非常有必要掌握基础用法。