如果你是 Golang 新手,你有可能几乎在任何地方都见过这个模块,但有时会发现它令人困惑,甚至是抽象的。
事实是,这个模块无处不在是有原因的。它在维护你的应用程序性能方面起着至关重要的作用。
在这篇博文中,我将为你省去翻阅源代码的麻烦,并告诉你关于该模块的一切!context
让我们开始吧!
一、为什么我们需要 Context ?
想象一下,你是一家餐厅的接单员。
当一个订单到达时,你把它委托给众多厨师中的一个。
如果顾客突然决定走开,你会怎么做?
毫无疑问,你会阻止你的厨师进一步处理这个订单,以防止任何原料的浪费!
这就是这个 context 的作用!
它是传递给你的函数和 Goroutines 的一个参数,并允许你在不再需要它们时及时停止它们。
二、什么是 Context?
该模块的典型用法是当客户端终止与服务器的连接时。
如果终止发生在服务器正在进行一些繁重的工作或数据库查询时呢?
该模块允许这些处理在不再需要的情况下立即停止。
Context 使用可归结为三个主要部分:
- 侦听取消事件
- 发出一个取消事件
- 传递请求范围数据
让我们分别讨论这些内容。
三、监听取消事件
Context 类型只不过是一个接口,实现了四个简单的函数。
现在,我们先关注前两个:Done() 和 Err()
当 context 被 cancel 时,Done() 函数返回一个接收空 struct 的 channel。
Err() 函数在 cancel 的情况下返回一个非 nil 的 error,否则返回 nil。
使用这些函数,监听取消事件变得很容易。
1. 监听 Done() 通道
go
在上面的例子中,我们模拟了一个网络服务处理器。
我们用 time.After() 来模拟了一个需要两秒钟处理一个请求的函数。
如果 context 在两秒内被取消,ctx.Done() 会收到一个空的 struct 。第二种情况将被执行,并且退出函数。
你可以在你的本地启动这段代码。一旦启动,在你的浏览器上访问 localhost:8000,然后在两秒内关闭它。观察你的终端,看看会发生什么。
2. 检查 Err() 的错误
另外,你可以在执行一些关键逻辑之前从 ctx.Err() 检查错误。
如果上下文被取消了,上面的函数就会停止并返回。
四、发出一个取消事件
context 提供了三个返回 CancelFunc 的函数。
调用 cancelFunc 会向 ctx.Done() 通道发射一个空的 struct,并通知监听它的下游函数。
在深入研究它们之前,我们先来谈谈上下文树(context tree)和根上下文(root context)。
1. Context Tree
当你调用这些 WithX 函数时,它们接受一个父级 context,并返回一个带有新的 Done 通道的 context 副本。
less
在上面的例子中,我们创建了一个多上下文树(context tree)。
当我们调用 cancelFunc1,我们将取消 child1Ctx 和 child3Ctx,child2Ctx不受影响。
2. Root context
由于这些函数需要一个父 context 作为参数,context 提供了两个简单的函数来创建 root context。
这些函数输出一个空的 context,完全不做任何事情。它不能被取消,也不能携带值。
它们的主要目的是作为一个根 context,以后可以传递给任何一个 WithX 函数来创建一个可取消的 context。
3. WithCancel
go
WithCancel 函数接收一个父 context,并返回一个可取消的 contet 和一个取消函数。
go
如果 databaseQuery 返回一个错误,cancel() 将被调用。 然后 operation1 函数将通过 ctx.Done() 被通知并优雅地退出。
你可以通过 ctx.Err() 找到取消的原因。
4. WithTimeout
WithTimeout 允许你指定一个超时时间,如果超过这个时间,就自动取消上下文。
go
在上面的例子中,上下文将在三秒后自动取消。
因此,如果在这之前数据库查询没有成功,处理程序将退出并返回。
或者,你也可以通过 cancel() 函数手动取消上下文。
5. WithDeadline
WithDeadline 函数接受一个特定的超时时间,而不是一个持续时间。除此之外,它的工作原理与 WithTimeout 完全相似。
go
上面例子中的上下文将在三秒后自动取消。
五、传递请求范围数据
正如我们通常在函数之间传递 ctx 变量一样,请求范围数据可以使用 WithValue 函数标记该变量。
考虑到一个涉及多个函数调用的应用程序,我们可以通过 ctx 变量将 traceID 传递给这些函数以进行监控和打印日志。
go
WithValue 函数可以将值添加到 ctx 变量中,然后通过 ctx.Value 函数可以读取出来。
六、注意事项和做法
虽然很方便,但 context 经常被误用,并且很容易给你的应用程序带来错误。
在我们结束这篇文章之前,让我们谈谈一些基本的做法。
1. 总是在 defer 中调用 cancel()
css
当你通过 WithCancel 函数生成一个新的可取消的 context 时,该模块将:
- 如果调用了 cancel(),将取消事件传播给所有的子 Goroutine
- 跟踪父 context 结构中的所有子 context
如果一个函数在没有调用 cancel() 的情况下返回,Goroutine和 子的 context 将无限期地留在内存中,导致内存泄漏。
这也适用于 WithTimeout 和 WithDeadline,除了这些函数在超过截止日期时自动取消上下文。
但是,对于任何一个函数来说,在 defer 中取消上下文仍然是一个最佳做法。
2. 只对请求范围数据使用 WithValue
go
我们很容易认为,我们是在最后一次函数调用中用 value2 覆盖了 value1。
然而,情况并非如此。
WithValue 函数接收了一个父级上下文并返回了一个副本。因此,它不是覆盖值,而是用一个新的键值对创建一个新的副本。
因此,你应该将 WithValue 的使用限制为有限的请求范围数据。
传递稍后将被改变的参数值将导致创建多个 context 变量,这会导致你的内存使用量大幅增加。
七、总结
关于 context 的讲解就到此为止!
go
上面的要点总结了 context 所提供的所有内容。
希望这篇文章对你有帮助,我们下次再会。
链接:https://juejin.cn/post/7130293077893185544
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。