菜鸟笔记
提升您的技术认知

深入理解golang context-ag真人游戏

阅读前需要掌握的内容

什么是pipeline( 流水线 )?

数据流水线充分利用了多核特性,代码层面是基于 channel 类型 和 go 关键字。

对于"流水线"这个概念,go语言中并没有正式的定义,它只是很多种并发方式的一种。这里我给出一个非官方的定义:一条流水线是 是由多个阶段组成的,相邻的两个阶段由 channel 进行连接

每个阶段是由一组在同一个函数中启动的 goroutine 组成。在每个阶段,这些 goroutine 会执行下面三个操作:

  • 通过 inbound channels 从上游接收数据
  • 对接收到的数据执行一些操作,通常会生成新的数据
  • 将新生成的数据通过 outbound channels 发送给下游

除了第一个和最后一个阶段,每个阶段都可以有任意个 inbound 和 outbound channel。显然,第一个阶段只有 outbound channel,而最后一个阶段只有 inbound channel。我们通常称第一个阶段为"生产者"或"源头",称最后一个阶段为"消费者"或"接收者"。

流水线进阶:扇入和扇出

  • 扇出:同一个 channel 可以被多个函数读取数据,直到channel关闭。
    这种机制允许将工作负载分发到一组worker,以便更好地并行使用 cpu 和 i/o。

  • 扇入:多个 channel 的数据可以被同一个函数读取和处理,然后合并到一个 channel,直到所有 channel都关闭。

在使用流水线函数时,有一个固定的模式:

  • 在一个阶段,当所有发送操作 (ch<-) 结束以后,关闭 outbound channel

  • 在一个阶段,goroutine 会持续从 inbount channel 接收数据,直到所有 inbound channel 全部关闭

在这种模式下,每一个接收阶段都可以写成 range 循环的方式,
从而保证所有数据都被成功发送到下游后,goroutine能够立即退出。

但我们日常需求更多是:当后一个阶段不需要数据时,上游阶段能够停止生产

如果一个阶段不能消费所有的 inbound 数据,试图发送这些数据的 goroutine 会永久阻塞。显然这样会存在资源泄漏。一方面goroutine 消耗内存和运行时资源,另一方面goroutine 栈中的堆引用会阻止 gc 执行回收操作。

为了保证在下游阶段接收数据失败时,上游阶段也能够正常退出。一个方式是使用带有缓冲的管道作为 outbound channel。缓存可以存储固定个数的数据。如果缓存没有用完,那么发送操作会立即返回。但是缓存机制一班会存在一些问题,会使得代码变得相对脆弱,为了从根本上解决这个问题,我们需要提供一种机制,让下游阶段能够告知上游发送者停止接收的消息。

显式取消 (explicit cancellation)

当 main 函数决定退出,并停止接收 out 发送的任何数据时,它必须告诉上游阶段的 goroutine 让它们放弃
正在发送的数据。 main 函数通过发送数据到一个名为 done 的channel实现这样的机制,同时为了能够让未知数目、且个数不受限制的goroutine 停止向下游发送数据。在go语言中,我们可以通过关闭一个channel 实现,因为在一个已关闭 channel 上执行接收操作(<-ch)总是能够立即返回,返回值是对应类型的零值。
因此。流水线通过两种方式解除发送者的阻塞:

  • 提供足够大的缓冲保存发送者发送的数据
  • 接收者放弃 channel 时,显式地通知发送者。

原文链接

context简介

在 go http包的server中,每一个请求在都有一个对应的 goroutine 去处理。请求处理函数通常会启动额外的 goroutine 用来访问后端服务,比如数据库和rpc服务。用来处理一个请求的 goroutine 通常需要访问一些与请求特定的数据,比如终端用户的身份认证信息、验证相关的token、请求的截止时间。 当一个请求被取消或超时时,所有用来处理该请求的 goroutine 都应该迅速退出,然后系统才能释放这些 goroutine 占用的资源。 context 包,就是专门用来简化对于处理单个请求的多个 goroutine 之间与请求域的数据、取消信号、截止时间等相关操作。

package context

context 包的核心是 struct context,声明如下:

// a context carries a deadline, cancelation signal, and request-scoped values
// across api boundaries. its methods are safe for simultaneous use by multiple
// goroutines.
type context interface {
    // done returns a channel that is closed when this `context` is canceled
    // or times out.
    done() <-chan struct{}
    // err indicates why this context was canceled, after the done channel
    // is closed.
    err() error
    // deadline returns the time when this context will be canceled, if any.
    deadline() (deadline time.time, ok bool)
    // value returns the value associated with key or nil if none.
    value(key interface{}) interface{}
}
  • done 方法返回一个 channel,这个 channel 对于以 context 方式运行的函数而言,是一个取消信号。当这个 channel 关闭时,上面提到的这些函数应该终止手头的工作并立即返回。 之后,err 方法会返回一个错误,告知为什么 context 被取消。

  • 一个 context 不能拥有 cancel 方法,同时我们也只能 done channel 接收数据。背后的原因是一致的:接收取消信号的函数和发送信号的函数通常不是一个。 一个典型的场景是:父操作为子操作操作启动 goroutine,子操作也就不能取消父操作。 作为一个折中,withcancel 函数 (后面会细说) 提供了一种取消新的 context 的方法。

  • context 对象是线程安全的,你可以把一个 context 对象传递给任意个数的 gorotuine,
    对它执行 取消 操作时,所有 goroutine 都会接收到取消信号。

  • deadline 方法允许函数确定它们是否应该开始工作。如果剩下的时间太少,也许这些函数就不值得启动。代码中,我们也可以使用 deadline 对象为 i/o 操作设置截止时间。

  • value 方法允许 context 对象携带request作用域的数据,该数据必须是线程安全的。

继承 context

context 包提供了一些函数,协助用户从现有的 context 对象创建新的 context 对象。
这些 context 对象形成一棵树:当一个 context 对象被取消时,继承自它的所有 context 都会被取消。

background 是所有 context 对象树的根,它不能被取消。它的声明如下:

// background returns an empty context. it is never canceled, has no deadline,
// and has no values. background is typically used in main, init, and tests,
// and as the top-level `context` for incoming requests.
func background() context

withcancel 和 withtimeout 函数 会返回继承的 context 对象, 这些对象可以比它们的父 context 更早地取消。

当请求处理函数返回时,与该请求关联的 context 会被取消。 当使用多个副本发送请求时,可以使用 withcancel取消多余的请求。 withtimeout 在设置对后端服务器请求截止时间时非常有用。

// withcancel returns a copy of parent whose done channel is closed as soon as
// parent.done is closed or cancel is called.
func withcancel(parent context) (ctx context, cancel cancelfunc)
// a cancelfunc cancels a context.
type cancelfunc func()
// withtimeout returns a copy of parent whose done channel is closed as soon as
// parent.done is closed, cancel is called, or timeout elapses. the new
// context's deadline is the sooner of now timeout and the parent's deadline, if
// any. if the timer is still running, the cancel function releases its
// resources.
func withtimeout(parent context, timeout time.duration) (context, cancelfunc)

withvalue 函数能够将请求作用域的数据与 context 对象建立关系。声明如下:

// withvalue returns a copy of parent whose value method returns val for key.
func withvalue(parent context, key interface{}, val interface{}) context

结论

在 google, 我们要求 go 程序员把 context 作为第一个参数传递给 入口请求和出口请求链路上的每一个函数。这种机制一方面保证了多个团队开发的 go 项目能够良好地协作,另一方面它是一种简单的超时和取消机制,保证了临界区数据 (比如安全凭证) 在不同的 go 项目中顺利传递。

如果你要在 context 之上构建服务器框架,需要一个自己的 context 实现,在框架与期望 context 参数的代码之间建立一座桥梁。
当然,client 库也需要接收一个 context 对象。在请求作用域数据与取消之间建立了通用的接口以后,开发者使用 context
分享代码、创建可扩展的服务都会非常方便。

网站地图