go-context笔记

WHY

每一个长请求都应该有个超时限制
需要在调用中传递这个超时
比如开始处理请求的时候我们说是 3 秒钟超时
那么在函数调用中间,这个超时还剩多少时间了?
需要在什么地方存储这个信息,这样请求处理中间可以停止

context

Context通常被译作上下文。
一般理解为程序单元的一个运行状态、现场、快照,而翻译中上下又很好地诠释了其本质,上下上下则是存在上下层的传递,上会把内容传递给下。在Go语言中,程序单元也就指的是Goroutine。
为了支持多Goroutine管理机制。
每个Goroutine在执行之前,都要先知道程序当前的执行状态,通常将这些执行状态封装在一个Context变量中,传递给要执行的Goroutine中。上下文则几乎已经成为传递与请求同生存周期变量的标准方法。在网络编程下,当接收到一个网络请求Request,处理Request时,我们可能需要开启不同的Goroutine来获取数据与逻辑处理,即一个请求Request,会在多个Goroutine中处理。而这些Goroutine可能需要共享Request的一些信息;同时当Request被取消或者超时的时候,所有从这个Request创建的所有Goroutine也应该被结束。

实现

  • context.Context:
    是不可变的(immutable)树节点
    Cancel 一个节点,会连带 Cancel 其所有子节点 (从上到下)
    Context values 是一个节点
    Value 查找是回溯树的方式 (从下到上)
  • context链
package main
func tree() {
  ctx1 := context.Background()
  ctx2, _ := context.WithCancel(ctx1)
  ctx3, _ := context.WithTimeout(ctx2, time.Second * 5)
  ctx4, _ := context.WithTimeout(ctx3, time.Second * 3)
  ctx5, _ := context.WithTimeout(ctx3, time.Second * 6)
  ctx6 := context.WithValue(ctx5, "userID", 12)
}
  • context.Context API
    3个函数用于限定什么时候你的子节点退出;
    1个函数用于设置请求范畴的变量
type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

Deadline会返回一个超时时间,Goroutine获得了超时时间后,例如可以对某些io操作设定超时时间。

Done方法返回一个信道(channel),当Context被撤销或过期时,该信道是关闭的,即它是一个表示Context是否已关闭的信号。

当Done信道关闭后,Err方法表明Context被撤的原因。

Value可以让Goroutine共享一些数据,当然获得数据是协程安全的。但使用这些数据的时候要注意同步,比如返回了一个map,而这个map的读写则要加锁。

函数、接口、结构体

image.png

创建 Context

  • 在 RPC 开始的时候,使用 context.Background()
    有些人把在 main() 里记录一个 context.Background(),然后把这个放到服务器的某个变量里,然后请求来了后从这个变量里继承 context。这么做是不对的。直接每个请求,源自自己的 context.Background() 即可。
  • 如果你没有 context,却需要调用一个 context 的函数的话,用 context.TODO()
  • 如果某步操作需要自己的超时设置的话,给它一个独立的 sub-context(如前面的例子)

集成到API里

  • 如果有 Context,将其作为第一个变量。
func (d* Dialer) DialContext(ctx context.Context, network, address string) (Conn, error)

request 结构体应该以 Request 结束为生命终止, 当 RPC 请求处理结束后,应该去掉对 Context 变量的引用(Unreference)

  • 及时关闭
    ctx, cancel := context.WithTimeout(parentCtx, time.Second * 2)
    defer cancel()

context.Value API 的万金油(duct tape)

type valueCtx struct {
  Context
  key, val interface{}
}
func WithValue(parent Context, key, val interface{}) Context {
  //  ...
  return &valueCtx{parent, key, val}
}
func (c *valueCtx) Value(key interface{}) interface{} {
  if c.key == key {
    return c.val
  }
  return c.Context.Value(key)
}

WithValue() 实际上就是在 Context 树形结构中,增加一个节点.

约束 key 的空间

type privateCtxType string
var (
  reqID = privateCtxType("req-id")
)
func GetRequestID(ctx context.Context) (int, bool) {
  id, exists := ctx.Value(reqID).(int)
  return id, exists
}
func WithRequestID(ctx context.Context, reqid int) context.Context {
  return context.WithValue(ctx, reqID, reqid)
}

这里使用 WithXxx 而不是 SetXxx 也是因为 Context 实际上是 immutable 的,所以不是修改 Context 里某个值,而是产生新的 Context 带某个值。

  • Context.Value 是 immutable 的,context.Context 从设计上就是按照 immutable (不可变的)模式设计的,同样,Context.Value 也是 immutable 的。
  • Context.Value 与request同生共死。
    Request 数据衍生出来,并且随着 Request 的结束而终结
  • 注意
    对于调试非常方便
    将必须的信息放入 Context.Value 中,会让接口定义更加不透明
    如果可以尽量明确定义在接口
    尽量不要用 Context.Value

参考

机制 https://www.cnblogs.com/zhangboyu/p/7456606.html
https://zhuanlan.zhihu.com/p/68792989
https://www.jianshu.com/p/ca7f0629de77
https://studygolang.com/articles/23247?fr=sidebar

你可能感兴趣的:(go-context笔记)