Go语言之Context

Python026

Go语言之Context,第1张

golang在1.6.2的时候还没有自己的context,在1.7的版本中就把golang.org/x/net/context包被加入到了官方的库中。中文译作“上下文”,它主要包含了goroutine 的运行状态、环境等信息。

context 主要用来在 goroutine 之间传递上下文信息,包括:同步信号、超时时间、截止时间、请求相关值等。

该接口定义了四个需要实现的方法

如果有个网络请求Request,然后这个请求又可以开启多个goroutine做一些事情,当这个网络请求出现异常和超时时,这个请求结束了,这时候就可以通过context来跟踪这些goroutine,并且通过Context来取消他们,然后系统才可回收所占用的资源。

为了更方便的创建Context,包里头定义了Background来作为所有Context的根,它是一个emptyCtx的实例。

Background返回一个非空的Context。它永远不会被取消。它通常用来初始化和测试使用,作为一个顶层的context,也就是说一般我们创建的context都是基于Background。

TODO返回一个非空的Context。当不清楚要使用哪个上下文的时候可以使用TODO。

他们两个本质上都是emptyCtx结构体类型,是一个不可取消,没有设置截止时间,没有携带任何值的Context。

有了如上的根Context,那么是如何衍生更多的子Context的呢?这就要靠context包为我们提供的With系列的函数了。

通过这些函数,就创建了一颗Context树,树的每个节点都可以有任意多个子节点,节点层级可以有任意多个。

WithCancel函数,最常用的派生 context 方法。该方法接受一个父 context。父 context 可以是一个 background context 或其他 context。

WithDeadline函数,该方法会创建一个带有 deadline 的 context。当 deadline 到期后,该 context 以及该 context 的可能子 context 会受到 cancel 通知。另外,如果 deadline 前调用 cancelFunc 则会提前发送取消通知。

WithTimeout和WithDeadline基本上一样,这个表示是超时自动取消,是多少时间后自动取消Context的意思。

WithValue函数和取消Context无关,它是为了生成一个绑定了一个键值对数据的Context,这个绑定的数据可以通过Context.Value方法访问到,一般我们想要通过上下文来传递数据时,可以通过这个方法,如我们需要tarce追踪系统调用栈的时候。

使用Context的程序应遵循以下规则,以使各个包之间的接口保持一致:

1.不要将 Context 塞到结构体里。直接将 Context 类型作为函数的第一参数,而且一般都命名为 ctx。

2.不要向函数传入一个 nil 的 context,如果你实在不知道传什么,标准库给你准备好了一个 context:todo。

3.不要把本应该作为函数参数的类型塞到 context 中,context 存储的应该是一些共同的数据。例如:登陆的 session、cookie 等。

4.同一个 context 可能会被传递到多个 goroutine,别担心,context 是并发安全的。

上图是Golang官网FAQ的部分截图,看来关于Go不支持重载的这个问题困扰了很多从面向对象语言转到Go的开发者。官方在这里做出了解答。

在上面的回答中有这样一句话:

其意思是: 使用其他语言的经验告诉我们,使用具有相同名称但签名不同的多种方法有时会很有用,但在实践中也可能会造成混淆和脆弱。

接下来又说: 在Go的类型系统中,仅按名称进行匹配并要求类型一致是一个简化的主要决定。

最后一句话: 关于操作员重载,似乎比绝对要求更方便。 同样,没有它,事情会变得更简单。

整个的解答非常漂亮、简洁。我们看完之后就会理解,Go语言的设计者之所以没有在Go中实现方法的重载,并没有复杂的理由,核心原则就是: 让Go保持足够的简单。 这也能看出来Go语言的设计者有着极大的选择和克制。

其实,笔者认为重载在本质上并没有很大的实际意义。只是表现力和表现形式上有一定的差别。明确某个上下文中的函数调用的关键就是函数签名,支持重载的语言中一般是函数名加函数参数构成函数签名。而Go中可以认为函数名就是签名。逻辑上没有太大的区别,就是把工作做在了台前 还是幕后的区别。

当然如果非要较真的话,我们或许可以在Go中声明方法的时候将参数写成 interface{} 或者 ... 切片的方式。在传进来参数的时候做一步校验,判断参数的类型和个数,然后分别处理之。

仁者见仁智者见智,大家有什么不同的理解欢迎一起沟通。