由于goroutine,go的并发很是方便,可是这也带来了另一个问题,当咱们进行一个耗时的异步操做时,如何在约定的时间内终止该操做并返回一个自定义的结果?这也是你们常说的咱们如何去终止一个goroutine(由于goroutine不一样于os线程,没有主动interrupt机制),这里就轮到今天的主角context登场了。git
context源于google,于1.7版本加入标准库,按照官方文档的说法,它是一个请求的全局上下文,携带了截止时间、手动取消等信号,并包含一个并发安全的map用于携带数据。context的API比较简单,接下来我会在具体的使用场景中进行介绍。github
通常来讲,咱们的根context会在请求的入口处构造以下golang
ctx := context.Background()
复制代码
若是拿捏不许是否须要一个全局的context,可使用下面这个函数构造安全
ctx := context.TODO()
复制代码
可是不能够为nil。bash
传值使用方式以下网络
package main
import (
"context"
"fmt"
)
func func1(ctx context.Context) {
ctx = context.WithValue(ctx, "k1", "v1")
func2(ctx)
}
func func2(ctx context.Context) {
fmt.Println(ctx.Value("k1").(string))
}
func main() {
ctx := context.Background()
func1(ctx)
}
复制代码
咱们在func1经过WithValue(parent Context, key, val interface{}) Context,赋值k1为v1,在其下层函数func2经过ctx.Value(key interface{}) interface{}获取k1的值,比较简单。这里有个疑问,若是我是在func2里赋值,在func1里面可以拿到这个值吗?答案是不能,context只能自上而下携带值,这个是要注意的一点。并发
能够考虑这样一个问题,若是没有context包,咱们如何取消一个耗时操做呢?我这里模拟了两种写法异步
timeout := 10 * time.Second
t = time.Now().Add(timeout)
conn.SetDeadline(t)
复制代码
package main
import (
"errors"
"fmt"
"time"
)
func func1() error {
respC := make(chan int)
// 处理逻辑
go func() {
time.Sleep(time.Second * 3)
respC <- 10
}()
// 超时逻辑
select {
case r := <-respC:
fmt.Printf("Resp: %d\n", 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\n", err)
}
复制代码
以上两种方式在工程实践中也会常常用到,下面咱们来看下如何使用context进行主动取消、超时取消以及存在多个timeout时如何处理函数
package main
import (
"context"
"errors"
"fmt"
"sync"
"time"
)
func func1(ctx context.Context, wg *sync.WaitGroup) error {
defer wg.Done()
respC := make(chan int)
// 处理逻辑
go func() {
time.Sleep(time.Second * 5)
respC <- 10
}()
// 取消机制
select {
case <-ctx.Done():
fmt.Println("cancel")
return errors.New("cancel")
case r := <-respC:
fmt.Println(r)
return nil
}
}
func main() {
wg := new(sync.WaitGroup)
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
go func1(ctx, wg)
time.Sleep(time.Second * 2)
// 触发取消
cancel()
// 等待goroutine退出
wg.Wait()
}
复制代码
package main
import (
"context"
"fmt"
"time"
)
func func1(ctx context.Context) {
hctx, hcancel := context.WithTimeout(ctx, time.Second*4)
defer hcancel()
resp := make(chan struct{}, 1)
// 处理逻辑
go func() {
// 处理耗时
time.Sleep(time.Second * 10)
resp <- struct{}{}
}()
// 超时机制
select {
// case <-ctx.Done():
// fmt.Println("ctx timeout")
// fmt.Println(ctx.Err())
case <-hctx.Done():
fmt.Println("hctx timeout")
fmt.Println(hctx.Err())
case v := <-resp:
fmt.Println("test2 function handle done")
fmt.Printf("result: %v\n", v)
}
fmt.Println("test2 finish")
return
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
func1(ctx)
}
复制代码
对于多个超时时间的处理,能够把上述超时取消例子中的注释打开,会观察到,当处理两个ctx时,时间短的会优先触发,这种状况下,若是只断定一个context的Done()也是能够的,可是必定要保证调用到两个cancel函数ui
以上是context剖析的上篇,主要从使用层面,让你们有一个直观的认识,这样在工程中能够进行灵活的使用,接下来会从源码层面进行剖析。