Golang基础笔记十三之context
本文首发于公众号:Hunter后端
原文链接:Golang基础笔记十三之context
在 Golang 里,context 包提供了很多比如传递截止时间、取消信号、传递数据等操作的标准方式,用于在跨 API 边界、进程和 goroutine之间进行。
这一篇笔记详细介绍一下 context 包相关的一些操作。
以下是本篇笔记目录:
- Context 接口及作用
- 取消传播
- 超时控制
- 截止时间
- 传递数据
1、Context 接口及作用
1. Context 接口
Context 是 context 包下的一个接口,其定义如下:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
- Deadline() 返回上下文被取消的时间
- Done() 返回一个通道,当上下文被取消时关闭
- Error() 返回上下文被取消的原因
- Value() 可以获取相应的键值对数据
下面所有的操作都是基于 context.Context 实现。
2. context 相关函数
我们可以使用 context 创建一些函数,用来实现取消、超时控制、传递数据等操作。
1) context.Background()
根上下文,可以作为所有上下文的起点,当我们需要实现取消、超时控制等功能,都需要定义一个父级上下文,这个时候我们就可以使用 context.Backgroud() 来创建。
2) context.TODO()
当我们不确定使用哪个上下文时,就可以使用 context.TODO(),但是在源代码中,它的实现与 context.Backgroud() 是一样的逻辑。
3) context.WithCancel(parent)
创建可以取消的上下文,参数是父级上下文,当我们调用一个函数,并且希望在某些时候取消这个调用,比如超时,这个时候我们可以使用这个函数,并手动进行取消。
4) context.WithTimeout(parent, timeout)
创建有超时的上下文,参数是父级上下文和 time.Duration,当我们调用一个函数,并且希望在多久以后可以自动取消,可以使用这个函数。
5) context.WithDeadline(parent, d)
创建有截止时间的上下文,参数是父级上下文和 time.Time,当我们调用一个函数,并且希望在某个具体的时间点可以自动取消,可以使用这个函数。
6) context.WithValue(parent, key, value)
创建带有键值对的上下文,我们希望通过上下文传递数据,就可以使用这个函数。
这里介绍了 context 相关的一些函数,接下来我们以具体的代码为示例,分别用这些函数来实现对应的功能。
2、 取消传播
我们使用 context.WithCancel() 函数实现取消传播的功能。
其实就是取消函数执行的操作,为什么会说取消传播,因为上下文可以在函数调用中一层一层传递,当我们取消了根上下文,所有调用链中的上下文都会被取消。
context.WithCancel() 的使用代码示例如下:
ctx, cancel := context.WithCancel(context.Background())
这个函数返回两个结果,一个是上下文参数,一个是取消函数,我们可以使用取消函数在特定节点取消这个上下文,这里取消操作需要我们手动执行。
下面我们实现一个取消操作,我们调用两个函数,这两个函数随机执行一段时间,然后某个函数先返回结果,返回之后我们立马执行上下文的取消操作,
package main
import (
"context"
"fmt"
"math/rand"
"time"
)
func F1(ctx context.Context, ch chan string) {
sleepSeconds := rand.Intn(3)
time.Sleep(time.Duration(sleepSeconds) * time.Second)
ch <- "f1 result"
}
func F2(ctx context.Context, ch chan string) {
sleepSeconds := rand.Intn(3)
time.Sleep(time.Duration(sleepSeconds) * time.Second)
ch <- "f2 result"
}
func CallF1(ctx context.Context, ch chan string) {
chF1 := make(chan string)
go F1(ctx, chF1)
select {
case result := <-chF1:
ch <- result
case <-ctx.Done():
fmt.Println("F1 函数调用超时")
}
}
func CallF2(ctx context.Context, ch chan string) {
chF2 := make(chan string)
go F2(ctx, chF2)
select {
case result := <-chF2:
ch <- result
case <-ctx.Done():
fmt.Println("F2 函数调用超时")
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ch1 := make(chan string)
ch2 := make(chan string)
go CallF1(ctx, ch1)
go CallF2(ctx, ch2)
select {
case r1 := <-ch1:
fmt.Println("f1 调用完成: ", r1)
cancel()
case r2 := <-ch2:
fmt.Println("f2 调用完成: ", r2)
cancel()
}
}
在这里,我们目标调用函数为 F1() 和 F2(),使用 CallF1() 和 CallF2() 作为中间调用函数,在其中使用 select-case 等待目标函数返回结果,并同时监听 ctx.Done() 判断 ctx 是否已经取消。
同时在 main() 函数中监听 ch1 和 ch2 判断哪个通道先返回结果,监听到返回结果马上取消 ctx 上下文,main() 函数就可以接着往下执行,避免两个目标函数其中一个长时间执行。
这里需要注意的是,中间函数 CallF1() 和 CallF2() 虽然被取消了,但 F1()、F2() 作为执行的 goroutine 并没有取消执行,如果有取消的需求,可以在 F() 函数内部伺机监听 ctx.Done() 以提前退出函数。
3、 超时控制
我们使用 context.WithTimeout(parent, timeout) 可以实现超时控制。
下面实现一个功能为,调用某个函数,并给一秒的超时时间,超过时间则进行超时处理,避免长时间堵塞,其中目标执行函数为 TargetFunc,其中,随机 sleep 三秒内的时间。
整体代码如下:
package main
import (
"context"
"fmt"
"math/rand"
"time"
)
func TargetFunc() string {
sleepSeconds := rand.Intn(3)
time.Sleep(time.Duration(sleepSeconds) * time.Second)
return "result"
}
func CallFunc(ch chan string) {
ch <- TargetFunc()
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
ch := make(chan string)
defer close(ch)
go CallFunc(ch)
select {
case result := <-ch:
fmt.Println("result: ", result)
case <-ctx.Done():
fmt.Println("函数调用超时")
}
}
其中,TargetFunc 函数内部的逻辑为随机休息三秒内的时间,可能导致超时,也可能不超时。
CallFunc() 函数用作中间函数,用于获取目标函数调用结果并通过 channel 返回。
在 main 函数中,首先定义一个有超时时间的上下文,设置了一秒超时,然后定义一个通道,将其传给 CallFunc() 用于返回数据。
之后通过 select-case 操作,进入循环等待状态,用于判断 goroutine 中的 channel 和 ctx 的超时哪个先返回结果。
如果 TargetFunc 函数调用时间过长,ctx 超时控制的上下文先到期,那么则会打印出 函数调用超时 的信息,否则会打印出函数调用返回的结果。
4、 截止时间
我们可以使用 context.WithDeadline(parent, d) 函数实现一个有截止时间的上下文,它的调用参数第二个为 d,是一个具体的 time.Time 类型。
但其实,在背后的源码中,context.WithTimeout() 函数会将其中的 time.Duration 参数处理通过 time.Now().Add(timeout) 的方式处理成 time.Time 然后再调用 WithDeadline 函数,所以超时控制和截止时间这两个函数在本质上的逻辑是一致的。
所以这里我们的代码示例,也只是把输入的参数修改一下,即可实现功能,如下:
deadline := time.Now().Add(1 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
5、 传递数据
我们可以通过 context.WithValue() 函数实现传递数据的功能,这个函数接收三个参数,父级上下文,key 和 value,以下是使用示例:
ctx := context.Background()
ctxWithValue := context.WithValue(ctx, "str1", "value1")
ctxWithValue = context.WithValue(ctxWithValue, "slice", []int{1, 2, 3})
在获取数据的时候,我们可以使用 ctx.Value() 函数获取:
func ProcessCtxValue(ctx context.Context) {
value1 := ctx.Value("str1")
fmt.Println("str1 value: ", value1)
value2 := ctx.Value("slice")
fmt.Println("slice value: ", value2)
}
但是在真正用到这些数据的时候,我们还需要对 value 进行类型判断,可以直接如下操作:
func ProcessCtxValue(ctx context.Context) {
value1, ok := ctx.Value("str1").(string)
if ok {
fmt.Println("str1 value: ", value1)
}
value2 := ctx.Value("slice").([]int)
if ok {
fmt.Println("slice value: ", value2, value2[1])
}
}
Golang基础笔记十三之context的更多相关文章
- Golang基础笔记
<基础> Go语言中的3个关键字用于标准的错误处理流程: defer,panic,recover. 定义一个名为f 的匿名函数: Go 不支持继承和重载. Go的goroutine概念:使 ...
- MYSQL基础笔记(五)- 练习作业:站点统计练习
作业:站点统计 1.将用户的访问信息记录到文件中,独占一行,记录IP地址 <?php //站点统计 header('Content-type:text/html;charset=utf-8'); ...
- java之jvm学习笔记十三(jvm基本结构)
java之jvm学习笔记十三(jvm基本结构) 这一节,主要来学习jvm的基本结构,也就是概述.说是概述,内容很多,而且概念量也很大,不过关于概念方面,你不用担心,我完全有信心,让概念在你的脑子里变成 ...
- golang学习笔记8 beego参数配置 打包linux命令
golang学习笔记8 beego参数配置 打包linux命令 参数配置 - beego: 简约 & 强大并存的 Go 应用框架https://beego.me/docs/mvc/contro ...
- go语言,golang学习笔记1 官网下载安装,中文社区,开发工具LiteIDE
go语言,golang学习笔记1 官网下载安装,中文社区,开发工具LiteIDE Go语言是谷歌2009发布的专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速 ...
- Golang基础教程
以下使用goland的IDE演示,包含总计的golang基础功能共20个章节 一.go语言结构: 二.go基础语法: 三.变量 四.常量 五.运算符 六.条件语句 七.循环 八.函数 九.变量作用域 ...
- Bootstrap <基础二十三>页面标题(Page Header)
页面标题(Page Header)是个不错的功能,它会在网页标题四周添加适当的间距.当一个网页中有多个标题且每个标题之间需要添加一定的间距时,页面标题这个功能就显得特别有用.如需使用页面标题(Page ...
- Java基础笔记 – Annotation注解的介绍和使用 自定义注解
Java基础笔记 – Annotation注解的介绍和使用 自定义注解 本文由arthinking发表于5年前 | Java基础 | 评论数 7 | 被围观 25,969 views+ 1.Anno ...
- golang基础知识之encoding/json package
golang基础知识之json 简介 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式.可以去json.org 查看json标准的清晰定义.json pack ...
- python3.4学习笔记(十三) 网络爬虫实例代码,使用pyspider抓取多牛投资吧里面的文章信息,抓取政府网新闻内容
python3.4学习笔记(十三) 网络爬虫实例代码,使用pyspider抓取多牛投资吧里面的文章信息PySpider:一个国人编写的强大的网络爬虫系统并带有强大的WebUI,采用Python语言编写 ...
随机推荐
- K8s新手系列之Pod容器中的command和args指令
概述 command和args是containers下的两个指令,类似Dockerfile中的ENTRYPONIT和CMD指令. 官方文档地址:https://kubernetes.io/zh-cn/ ...
- idea格式化代码快捷键
Ctrl+Alt+L Ctrl+Shift+Alt+L
- 【FAQ】HarmonyOS SDK 闭源开放能力 — PDF Kit
1.问题描述: 预览PDF文件,文档上所描述的loadDocument接口,可以返回文件的状态,并无法实现PDF的预览,是否有能预览PDF相关接口? 解决方案: 1.执行loadDocument进行加 ...
- 信息资源管理综合题之“公钥密码体系中同一个用户拥有的密钥特点 和 如何使用密钥加解密才能保证传输数据的机密性 和 如何身份认证 和 CA的作用”
一.公钥密码体制在认证技术中是广泛使用的.结合加密和认证技术知识回答以下问题: 1.公钥密码体系中同一个用户拥有的密钥的特点是什么? 2.假设A.B是公钥密码体系的用户,A向B发送数据,A.B之间如何 ...
- TVM: 编译流程
深度学习编译器介绍 每一种硬件对应一门特定的编程语言,再通过特定的编译器去进行编译产生机器码,那随着硬件和语言的增多,编译器的维护难度会有很大困难.现代编译器已经解决了这个问题. 为了解决这个问题,科 ...
- 洛谷P2024 [NOI2001]食物链 种类并查集
洛谷P2024 [NOI2001]食物链 题目描述 见食物链 - 洛谷 \(n\le5*10^4\) \(k\le10^5\) Recollection 初中的时候想了一个假掉了的算法想了很久. 刚刚 ...
- Astah Community安装教程及使用说明(包括括菜单命令解释、操作向导说明、快捷命令说明)
一.安装教程: 1.双击下载好的文件,允许安装(现在官网已经停刊免费的社区版了,如果想要下载社区版需要从其他地方寻找资源) 2.选择安装的语言,English,点击OK 3.next,接受协议,nex ...
- PI Errors and possible solutions
引自:https://wiki.scn.sap.com/wiki/display/ERPHCM/PI+Errors+and+possible+solutions 转至元数据起始 404 - Not ...
- Java 并发工具类核心使用场景深度解析
在 Java 并发编程中,java.util.concurrent(JUC)包提供的工具类是解决多线程协作.资源控制及任务调度的关键.本文聚焦同步协调.资源控制.线程协作.并行计算四大核心场景,系统解 ...
- 优化数据处理效率,解读 EasyMR 大数据组件升级
EasyMR 作为袋鼠云基于云原生技术和 Hadoop.Hive.Spark.Flink.Hbase.Presto 等开源大数据组件构建的弹性计算引擎.此前,我们已就其展开了多方位.多角度的详尽介绍. ...