Skip to content

content 语句

Content(上下文),可以理解为一种维护Go协程的东西 —— 我们可以创建、运行一个Go协程,缺乏一种”维护“的机制,便引入了 Content。

它主要针对以下三种情况:

  • 跨 goroutine 的取消信号与超时管理
  • 请求级别的元数据(如 traceID、用户认证信息)传递
  • 避免全局变量和隐式状态,保持函数签名清晰

假设有一个非常长的调用链,但是第一步出错了,正常情况下,Go协程会依旧把接下来的调用链给执行掉,然而这样非常浪费资源,以及可能会有其他问题(比如用户意外退出,但是数据库等没有关闭之类的)

content理解成一个带着特殊消息(取消/超时/完成/键值对 等)的只读接口,在各种调用链之类的中,可以将content传入,并且显式的监听这些属性,并且做出对应的操作,就是content

使用

使用一个content作为根节点,然后使用content包下的一些函数来"拓展"它,最后使用。(其实最开始看到根节点还有点蒙到底是什么

根节点

  1. content.Background():返回一个空的、永不取消、无超时、无值的 Context
  2. content.TODO():但语义上表示“还不确定用哪种 Context”

但是看源码定义:

go
type todoCtx struct{ emptyCtx }
type backgroundCtx struct{ emptyCtx }

在数据的结构上它们其实是一致的,只是表达的语义(阅读理解)上不一样。

  • Background 明确表示“顶层、永不取消”
  • TODO 明确表示“待定、临时占位”

派生

“派生”,其实就是在根节点的Content上加了点东西(嵌了下结构体)。

  1. content.WithCancel:提供一个cancel函数,可以手动发送停止信号
  2. context.WithDeadline:在指定的截止时间到达时自动取消子 Context
  3. context.WithTimeout:简化版的WithDeadline —— 默认从现在开始time.Now().Add(timeout)
  4. context.WithValue:可以传递一个键值对,

观察源码,可以发现,就是一个工厂方法来负责从根节点派生,给调用链提供取消信号、超时控制或键值传递(就是对应不同使用场景下的content

实际例子:

GO
func worker(ctx context.Context) {
    fmt.Printf("Start Working\n")
    for {
        select {
        case <-ctx.Done():
            fmt.Println("上下文发送退出信号, 退出, Err:", ctx.Err())
            return
        default:
            fmt.Printf("Working...\n")
            time.Sleep(300 * time.Millisecond)
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    go worker(ctx)
    time.Sleep(3 * time.Second)
    cancel()
    time.Sleep(500 * time.Millisecond)
    fmt.Println("Main 结束")
}

Released under the MIT License.