Context 函数调用


Go服务器程序 每个请求都有一个goroutine去处理   处理程序还要创建额外的goroutine 访问后端资源 比如数据库 RPC服务等
这些goroutine都是在处理同一个请求 所以需要访问共享的资源 如用户身份信息 认证token 请求截止时间等
如果请求超时或者被取消 所有goroutine都应该马上退出并释放相关的资源
Go的Context包 也就是上下文 来控制 goroutine 达到目的

Context使用

请求处理的过程 调用各层的函数 每层的函数 创建自己的routine 构成了routine树  如果由Context来控制上下文 context也应该反映并实现成一棵树
根节点
要创建context树 第一步要有一个根结点 context.Background 函数的返回值是一个空的context 作为树的根结点
它一般由接收请求的第一个routine创建 不能被取消 没有值 也没有过期时间
var (
background = new(emptyCtx)
todo       = new(emptyCtx)
)
//Background returns a non-nil, empty Context.
//It is never canceled, has no values, and has no deadline.
//It is typically used by the main function, initialization, and tests, and as the top-level Context for incoming requests.
func Background() Context {
  return background
}
type emptyCtx int
其中 emptyCtx  实现了 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
第一个参数都是父context 返回一个Context类型的值
这样层层创建出不同的节点 子节点是从复制父节点得到的 且根据接收的函数参数保存子节点的状态值 然后就可以将它传递给下层的routine了
package main
import (
"context"
"fmt"
"time"
)
func Println(ctx context.Context, a int) {
    for {
      fmt.Println("fmt.println in for select")        
        a = a+1
        select {
            case <-ctx.Done():
            fmt.Println("程序结束")
            return
            default:
            fmt.Println(a)
        }
    }
}
func main() {// 超时取消
    a := 1
    timeout := 1 * time.Second
    ctxBg := context.Background()
    ctx, _ := context.WithTimeout(ctxBg, timeout)
    Println(ctx, a)
    //time.Sleep(1 * time.Second) // 等待时候还会继续输出
}
func main(){// 手动取消
    a := 1
    ctx, cancelCtx := context.WithCancel(context.Background())
    go func() {
        time.Sleep(1 * time.Second)
        cancelCtx() // 在调用处主动取消
    }()
    Println(ctx, a)
    time.Sleep(1 * time.Second)
}

WithTimeout源码

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
 return WithDeadline(parent, time.Now().Add(timeout))
}//WithDeadline 的第二个参数和 WithCancel 的区别

验证超时

package main
import (
    "context"
    "fmt"
    "log"
    "time"
)
func doTimeOutStuff(ctx context.Context) {
    for {
        time.Sleep(1 * time.Second)
        if deadline, ok := ctx.Deadline(); ok {
            fmt.Println("deadline set : ", deadline.Unix())
            if time.Now().After(deadline) {
                log.Fatal("错误:", ctx.Err().Error())
            }
        }
        select {
        case <-ctx.Done():
            fmt.Println("Done")
            return
        default:
            fmt.Println("working")
        }
    }
}
func main() {
    ctx, _ := context.WithTimeout(context.Background(), 1*time.Second)
    fmt.Println("当前时间: ", time.Now().Unix())
    fmt.Println("超时时间: 1秒")
    go doTimeOutStuff(ctx)
    time.Sleep(10 * time.Second)
}//output
当前时间:1566381690
超时时间:1秒
deadline set :  1566381691
2019/08/21 18:01:31 错误:context deadline exceeded
验证是否超时做日志记录处理 或 根据业务做出相应的提示  在<-ctx.Done() 做好资源的控制

返回值 CancelFunc

调用 CancelFunc  取消child以及child生成的context 取出父context对这个child的引用 停止相关的计数器
package main
import (
"context"
"fmt"
"time"
)
var key string = "name"
func main() {
    ctx, cancel := context.WithCancel(context.Background())
    valueCtx := context.WithValue(ctx, key, "【监控1】")
    go watch(valueCtx)
    time.Sleep(10 * time.Second)
    fmt.Println("可以了 通知监控停止")
    cancel()//为了检测监控过是否停止 如果没有监控输出 就表示停止了
    time.Sleep(5 * time.Second)
}
func watch(ctx context.Context) {
    for {
    select {
        case <-ctx.Done():
            fmt.Println(ctx.Value(key), "监控退出 停止了...")
            return
        default:
            fmt.Println(ctx.Value(key), "goroutine监控中...")
            time.Sleep(2* time.Second)
    }
    }
}

Context 使用原则

不把Context放在结构体中 要以参数的方式传递
以Context作为参数的函数方法 应该把Context作为第一个参数 放在第一位
给函数方法传递Context的时候 不要传递nil 如果不知道传递什么 就使用context.TODO
Context的Value相关方法应该传递必须的数据 不要什么数据都使用这个传递
Context是线程安全的 放心 在多个goroutine中传递