Go 语言 context 都能做什么?
原文链接: Go 语言 context 都能做什么?
很多 Go 项目的源码,在读的过程中会发现一个很常见的参数 ctx,而且基本都是作为函数的第一个参数。
为什么要这么写呢?这个参数到底有什么用呢?带着这样的疑问,我研究了这个参数背后的故事。
开局一张图:
核心是 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,当 times out 或者调用 cancel 方法时。Err():返回一个错误,表示取消 ctx 的原因。Deadline():返回截止时间和一个 bool 值。Value():返回 key 对应的值。
有四个结构体实现了这个接口,分别是:emptyCtx, cancelCtx, timerCtx 和 valueCtx。
其中 emptyCtx 是空类型,暴露了两个方法:
func Background() Context
func TODO() Context
一般情况下,会使用 Background() 作为根 ctx,然后在其基础上再派生出子 ctx。要是不确定使用哪个 ctx,就使用 TODO()。
另外三个也分别暴露了对应的方法:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
遵循规则
在使用 Context 时,要遵循以下四点规则:
- 不要将 Context 放入结构体,而是应该作为第一个参数传入,命名为
ctx。 - 即使函数允许,也不要传入
nil的 Context。如果不知道用哪种 Context,可以使用context.TODO()。 - 使用 Context 的 Value 相关方法只应该用于在程序和接口中传递和请求相关的元数据,不要用它来传递一些可选的参数。
- 相同的 Context 可以传递给不同的 goroutine;Context 是并发安全的。
WithCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
WithCancel 返回带有新 Done 通道的父级副本。当调用返回的 cancel 函数或关闭父上下文的 Done 通道时,返回的 ctx 的 Done 通道将关闭。
取消此上下文会释放与其关联的资源,因此在此上下文中运行的操作完成后,代码应立即调用 cancel。
举个例子:
这段代码演示了如何使用可取消上下文来防止 goroutine 泄漏。在函数结束时,由 gen 启动的 goroutine 将返回而不会泄漏。
package main
import (
"context"
"fmt"
)
func main() {
// gen generates integers in a separate goroutine and
// sends them to the returned channel.
// The callers of gen need to cancel the context once
// they are done consuming generated integers not to leak
// the internal goroutine started by gen.
gen := func(ctx context.Context) <-chan int {
dst := make(chan int)
n := 1
go func() {
for {
select {
case <-ctx.Done():
return // returning not to leak the goroutine
case dst <- n:
n++
}
}
}()
return dst
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // cancel when we are finished consuming integers
for n := range gen(ctx) {
fmt.Println(n)
if n == 5 {
break
}
}
}
输出:
1
2
3
4
5
WithDeadline
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
WithDeadline 返回父上下文的副本,并将截止日期调整为不晚于 d。如果父级的截止日期已经早于 d,则 WithDeadline(parent, d) 在语义上等同于 parent。
当截止时间到期、调用返回的取消函数时或当父上下文的 Done 通道关闭时,返回的上下文的 Done 通道将关闭。
取消此上下文会释放与其关联的资源,因此在此上下文中运行的操作完成后,代码应立即调用取消。
举个例子:
这段代码传递具有截止时间的上下文,来告诉阻塞函数,它应该在到达截止时间时立刻退出。
package main
import (
"context"
"fmt"
"time"
)
const shortDuration = 1 * time.Millisecond
func main() {
d := time.Now().Add(shortDuration)
ctx, cancel := context.WithDeadline(context.Background(), d)
// Even though ctx will be expired, it is good practice to call its
// cancellation function in any case. Failure to do so may keep the
// context and its parent alive longer than necessary.
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err())
}
}
输出:
context deadline exceeded
WithTimeout
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
WithTimeout 返回 WithDeadline(parent, time.Now().Add(timeout))。
取消此上下文会释放与其关联的资源,因此在此上下文中运行的操作完成后,代码应立即调用取消。
举个例子:
这段代码传递带有超时的上下文,以告诉阻塞函数应在超时后退出。
package main
import (
"context"
"fmt"
"time"
)
const shortDuration = 1 * time.Millisecond
func main() {
// Pass a context with a timeout to tell a blocking function that it
// should abandon its work after the timeout elapses.
ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err()) // prints "context deadline exceeded"
}
}
输出:
context deadline exceeded
WithValue
func WithValue(parent Context, key, val any) Context
WithValue 返回父级的副本,其中与 key 关联的值为 val。
其中键必须是可比较的,并且不应是字符串类型或任何其他内置类型,以避免使用上下文的包之间发生冲突。 WithValue 的用户应该定义自己的键类型。
为了避免分配给 interface{},上下文键通常具有具体的 struct{} 类型。或者,导出的上下文键变量的静态类型应该是指针或接口。
举个例子:
这段代码演示了如何将值传递到上下文以及如何检索它(如果存在)。
package main
import (
"context"
"fmt"
)
func main() {
type favContextKey string
f := func(ctx context.Context, k favContextKey) {
if v := ctx.Value(k); v != nil {
fmt.Println("found value:", v)
return
}
fmt.Println("key not found:", k)
}
k := favContextKey("language")
ctx := context.WithValue(context.Background(), k, "Go")
f(ctx, k)
f(ctx, favContextKey("color"))
}
输出:
found value: Go
key not found: color
本文的大部分内容,包括代码示例都是翻译自官方文档,代码都是经过验证可以执行的。如果有不是特别清晰的地方,可以直接去读官方文档。
以上就是本文的全部内容,如果觉得还不错的话欢迎点赞,转发和关注,感谢支持。
官方文档:
源码分析:
- https://mritd.com/2021/06/27/golang-context-source-code/
- https://www.qtmuniao.com/2020/07/12/go-context/
- https://seekload.net/2021/11/28/go-context.html
推荐阅读:
- Go 语言 map 如何顺序读取?
- Go 语言 map 是并发安全的吗?
- Go 语言切片是如何扩容的?
- Go 语言数组和切片的区别
- Go 语言 new 和 make 关键字的区别
- 为什么 Go 不支持 []T 转换为 []interface
- 为什么 Go 语言 struct 要使用 tags
Go 语言 context 都能做什么?的更多相关文章
- Context都没弄明白,还怎么做Android开发?
Activity mActivity =new Activity() 作为Android开发者,不知道你有没有思考过这个问题,Activity可以new吗?Android的应用程序开发采用JAVA语言 ...
- Context都没弄明白,还怎么做Android开发
转载:https://www.jianshu.com/p/94e0f9ab3f1d Activity mActivity =new Activity() 作为Android开发者,不知道你有没有思考过 ...
- PHP语言学习之php做图片上传功能
本文主要向大家介绍了PHP语言学习之php做图片上传功能,通过具体的内容向大家展示,希望对大家学习php语言有所帮助. 今天来做一个图片上传功能的插件,首先做一个html文件:text.php < ...
- [语法]C语言中二维数组做输入参数
C语言中二维数组做输入参数时, 可以同时指定各维长度, 可以只指定第二维的长度, 不可以只指定第一维的长度, 不可以各维长度都不指定. 一句话总结:要指定至少指定第二维,都不指定是不行的. 具体栗子如 ...
- NodeJS什么都能做,为什么还要JAVA?
这张图看起来简单而且很好理解,但没尝试过,会有很多疑问. SPA模式中,后端已供了所需的数据接口,view前端已经可以控制,为什么要多加NodeJS这一层? 多加一层,性能怎么样? 多加一层,前端的工 ...
- Java 程序员每天都在做什么?
作为一名 在大.中.小微企业都待过 的 Java 开发者,今天和大家分享下自己在不同公司的工作日常和收获.包括一些个人积累的工作提升经验,以及一些 Java 学习的方法和资源. 先从我的第一份 Jav ...
- 微信小程序来了,小程序都能做些什么
2017年的微信大动作就是微信小程序了,到底小程序都能做些什么?这是很多人关注的热点,小程序开发对企业又有什么帮助呢?下面让厦门微信小程序开发公司来为你就分析下. 微信小程序与APP的关系 ...
- 腾讯QQ会员技术团队:人人都可以做深度学习应用:入门篇(下)
四.经典入门demo:识别手写数字(MNIST) 常规的编程入门有"Hello world"程序,而深度学习的入门程序则是MNIST,一个识别28*28像素的图片中的手写数字的程序 ...
- Go语言Context(设计及分析)
context简单概述: Go服务器的每个请求都有自己的goroutine,而有的请求为了提高性能,会经常启动额外的goroutine处理请求,当该请求被取消或超时,该请求上的所有goroutines ...
- 【腾讯Bugly干货分享】人人都可以做深度学习应用:入门篇
导语 2016年,继虚拟现实(VR)之后,人工智能(AI)的概念全面进入大众的视野.谷歌,微软,IBM等科技巨头纷纷重点布局,AI 貌似将成为互联网的下一个风口. 很多开发同学,对人工智能非常感兴趣, ...
随机推荐
- [JAVA/Maven/IDEA]解决JAR包冲突
1 前言 想必这个问题,诸多同仁都遇到过. 很不凑巧,这段时间咱也屡次撞上这问题好几次了. 因此,实在是有必要说说怎么解决好这问题了0.0 2 诊断:包冲突的异常信息特征 [类定义未发现错误] NoC ...
- DG:有多个备库如何切换
问题描述:有一数据库准备进行主备switchover切换,但是有两个备库,其中最早一个备库状态已经出现GAP,第二个备库状态正常 SQL> show parameter log_archive_ ...
- mysql迁移:xtrabackup迁移mysql5.7.32
问题描述:利用外部xtrabackup工具来做迁移mysql数据库,或者恢复数据库 xtrabackup迁移mysql 1.环境 mysql源库 mysql目标迁移库 IP 192.168.163.3 ...
- 记一次 .NET某医疗器械清洗系统 卡死分析
一:背景 1. 讲故事 前段时间协助训练营里的一位朋友分析了一个程序卡死的问题,回过头来看这个案例比较经典,这篇稍微整理一下供后来者少踩坑吧. 二:WinDbg 分析 1. 为什么会卡死 因为是窗体程 ...
- Java关键字以及标识符
Java中有许多关键字,关键字是什么意思呢? 我用自己的分析来表达一下吧. Java就是源自于生活的,我们都有自己的名字.所以它也会有许多的名字,每个名字都有各自不同的特性(作用),都是系统定义好的. ...
- python 编程规范有哪些?
Python 编程规范主要包括代码布局.命名规范.注释规范.函数编写规范等多个方面,下面给出一些常见的编程规范及其示例代码. 1. 代码布局规范 代码布局规范主要是指代码的缩进.行宽.空行.换行等方面 ...
- 希望所有计算机学生能看到这篇c语言教程
大部分程序员走入编程世界第一个学习的语言就是C语言. 作为一门古老的编程语言,c语言拥有48年的发展历程. 为什么要学习 C语言? C语言是学习计算机程序设计语言的入门语言.最全面的编程面试网站 C语 ...
- AGC061 F Perfect String
毒瘤出题人,史诗加强 AGC 的 F-- 然而我连原题都不会,所以只学了原题做法. 翻译一下题意就是给定一张循环网格图,求经过 \((0,0)\) 的闭合回路条数. 由于网格图中每一个位置都等价,所以 ...
- 自建CA和公共CA有什么不同?
据统计,全球有数百个公共CA,通常它们是按国家地区进行划分的.这类CA受大众的广泛认可和使用,也被称为公共信任的证书颁发机构.但是由于一些大型企业拥有许多站点,为了更轻松高效的管理以及考虑到维护成本, ...
- 2020-12-06:mysql中,多个索引会有多份数据吗?
福哥答案2020-12-06: 数据不会有多份,索引有几个就有几份.聚簇索引存数据和索引,非聚簇索引存索引,聚簇索引只有一个,非聚簇索引可以有多个.