context包定义了 Context 接口,Context 的具体实现包括 4 个方法,分别是Deadline、Done、Err 和 Value,如下所示:
下面来具体解释下这 4 个方法。
Deadline 方法会返回这个 Context 被取消的截止日期。如果没有设置截止日期,ok 的值是 false。后续每次调用这个对象的 Deadline 方法时,都会返回和第一次调用相同的结果。
Done 方法返回一个 Channel 对象。在 Context 被取消时,此 Channel 会被 close,如果没被取消,可能会返回 nil。后续的 Done 调用总是返回相同的结果。当 Done 被 close的时候,你可以通过 ctx.Err 获取错误信息。Done 这个方法名其实起得并不好,因为名字太过笼统,不能明确反映 Done 被 close 的原因,因为 cancel、timeout、deadline 都可能导致 Done 被 close,不过,目前还没有一个更合适的方法名称。
关于 Done 方法,你必须要记住的知识点就是:如果 Done 没有被 close,Err 方法返回nil;如果 Done 被 close,Err 方法会返回 Done 被 close 的原因。
Value 返回此 ctx 中和指定的 key 相关联的 value。
Context 中实现了 2 个常用的生成顶层 Context 的方法。
context.Background():返回一个非 nil 的、空的 Context,没有任何值,不会被cancel,不会超时,没有截止日期。一般用在主函数、初始化、测试以及创建根Context 的时候。
context.TODO():返回一个非 nil 的、空的 Context,没有任何值,不会被 cancel,不会超时,没有截止日期。当你不清楚是否该用 Context,或者目前还不知道要传递一些什么上下文信息的时候,就可以使用这个方法。
官方文档是这么讲的,你可能会觉得像没说一样,因为界限并不是很明显。其实,你根本不用费脑子去考虑,可以直接使用 context.Background。事实上,它们两个底层的实现是一模一样的:
在使用 Context 的时候,有一些约定俗成的规则。
1. 一般函数使用 Context 的时候,会把这个参数放在第一个参数的位置。从来不把 nil 当做 Context 类型的参数值,可以使用 context.Background() 创建一个空的上下文对象,也不要使用 nil。
2.Context 只用来临时做函数之间的上下文透传,不能持久化 Context 或者把 Context长久保存。把 Context 持久化到数据库、本地文件或者全局变量、缓存中都是错误的用法。
3.key 的类型不应该是字符串类型或者其它内建类型,否则容易在包之间使用 Context 时候产生冲突。使用 WithValue 时,key 的类型应该是自己定义的类型。
4.常常使用 struct{}作为底层类型定义 key 的类型。对于 exported key 的静态类型,常常是接口或者指针。这样可以尽量减少内存分配。