go语言并发编程(五) ——Context

ops/2024/9/23 12:19:53/

Context(上下文)

前言

Context是go语言中所提供的一种并发控制的解决方案,相比于管道与WaitGroup,Context可以更好的控制子孙协程以及层次更深的协程。Context本身是一个接口,只要我们实现了该接口都可以被称为上下文,context标准库本身也提供了几个实现:

  • emptyCtx
  • cancelCtx
  • timerCtx
  • valueCtx

什么是Context

在看Context的具体实现之前,先来看看Context接口的定义.

type Context interface{Deadline(deadline time.Time, ok bool)Done() <-chan struct{}Err() errorValue(key any) any
}

我们来看一看这个接口里面所定义的四个方法:

  • Deadline
    该方法有两个返回值,deadline是截止时间,也就是上下文截止的时间,第二个值是是否设置dedline,如果没有则一直为false.
  • Done
    返回值是一个空结构体的只读管道,该管道仅仅起到通知作用,不传递任何数据,当上下文所做的工作要取消的时候,该通道就会被关闭,对于一些不支持取消的上下文,可能会返回nil
  • Err
    该方法会返回一个error,用来表示上下关闭的原因,如果管道没有关闭,则返回nil,如果关闭的话,则返回一个error,用来表示上下文关闭的原因。
  • Value
    该方法返回对应的键值,如果key不存在,或者不支持该方法,就会返回nil。

下面我们来看一个简单的例子:

package mainimport ("context""fmt""sync""time"
)var wa sync.WaitGroup
var stop bool
var RW sync.RWMutexfunc cpuIInfo(ctx context.Context) {defer wa.Done()for {select {case <-ctx.Done():fmt.Println("cpu info exit")returndefault:time.Sleep(2 * time.Second)fmt.Println("cpu info")}}
}func main() {wa.Add(1)ctx, cancel := context.WithCancel(context.Background())go cpuIInfo(ctx)time.Sleep(6 * time.Second)cancel()fmt.Println("main exit")wa.Wait()
}

输出为:

cpu info
cpu info
main exit
cpu info
cpu info exit

或许现在你不是很清楚上面的例子,但是看完今天的博文以后,相信大家就能和好的理解了话不多说,让我们来看一下有关context的具体内容:

emptyCtx

顾名思义,emptyCtx指的就是空的上下文,context包下所有的实现其实都是不对外暴露的,所以我们无法直接创建context.Context,但是go语言提供了对应的函数区创建上下文,例如下面我们可以利用context.Background()context.TODO()函数来创建一个空的上下文:两个函数的具体实现如下:

var{background =new(emptyCtx)todo =new(emptyCtx)
}func Background()Context{return background
}func TODO()Context{return todo
}

我们再来看看emptyCtx四个函数的实现:

type  emptyCtx intfunc (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}func () Done() <-chan struct{} {
return nil
}func () Value(key any) any {
return nil
}func (emptyContext) Err() error {
return nil
}

我们仔细观察emptyCtx的实现,发现其实emptyCtx仅仅返回了emptyCtx指针.emptyCtx的底层类型是int,之所以不使用空结构体,在之前我们提到过空结构体没有字段,不占用内存,但是我们要求emptyCtx的实例都要有自己的内存地址,而在它的方法中,由于它不能被取消,所以它没有deadline,由于它不能被取值,所以它实现的方法都是返回nil.emptyCtx通常是用来当作最顶层的上下文,在创建其他三种上下文时作为父上下文传入。

valueCtx

valueCtx的实现比较简单,它的内部只包括一对键值对,和一个内嵌的Context字段:

type valueCtx struct{Contextkay,value any
}

它自身也实现了Value方法,基本逻辑其实也很简单:找不到就去喊爸爸(去父上下文找):

func (c *valueCtx) Value(key any) any{if c.key==key{return c}return value(c.Context,key)
}

我们可以来看一个简单的例子:

package mainimport ("context""sync""time"
)var w sync.WaitGroupfunc main() {w.Add(1)go Do(context.WithValue(context.Background(), 1, 2))w.Wait()
}func Do(ctx context.Context) {ticker := time.NewTimer(2 * time.Second)defer w.Done()for {select {case <-ticker.C:println("time out")returncase <-ctx.Done():default:println(ctx.Value(1).(int))}time.Sleep(100 * time.Millisecond)}
}

输出为:

2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
time out

valueCtx多用于在多级协程中传递一些数据,无法被取消,因此ctx.Done永远会返回nil,select会忽略掉nil管道。

cancelCtx

cancelCtx以及timerCtx都实现了canceler接口,接口类型如下:

type canceler interface {// removeFromParent 表示是否从父上下文中删除自身// err 表示取消的原因cancel(removeFromParent bool, err, cause error)// Done 返回一个管道,用于通知取消的原因Done() <-chan struct{}
}

我们查看上面的源码可以看出来:cancel方法本身不对外暴露,但是会在我们创建上下文的时候通过闭包来将其封装成返回值供外界调用,这个在源码中也有所体现:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {if parent==nil{panic("cannot create context from nil parent")}c:=newCancelCtx(parent)//尝试将自身添加进父级的children中propagateCancel(parent,&c)return &c,func(){c.cancel(true,context.Canceled,nil)}
}

cancelCtx我们可以理解为一个可取消的上下文,它在创建的时候如果父级实现了canceler,就会将自身添加进父级的children中,否则就一直向上查找。如果所有的父级都没有实现canceler,就会启动一个协程等待父级取消,然后当父级结束时取消当前上下文。当调用cancelFunc时,Done通道将会关闭,该上下文的任何子级也会随之取消,最后会将自身从父级中删除。下面我们来看个例子:

package mainimport ("context""fmt""sync""time"
)var w sync.WaitGroupfunc main() {bkg := context.Background()ctx, cancel := context.WithCancel(bkg)w.Add(1)go func(ctx2 context.Context) {defer w.Done()for {select {case <-ctx.Done():fmt.Println(ctx.Err())returndefault:fmt.Println("等待取消中...")}time.Sleep(time.Millisecond * 200)}}(ctx)time.Sleep(time.Second * 3)cancel()w.Wait()
}

输出为:

等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
context canceled

timerCtx

相对于cancelCtx,timerCtx多了超时机制,context包下提供了两种创建的函数:

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)

这两个函数的功能类似,前者是指定一个具体的时间而后者则是指定一个时间间隔。timeCtx会在时间到期后自动取消上下文,取消的流程除了要额外的关闭timer之外,基本与cancelCtx一致,我们来看一个简单的示例:

package mainimport ("context""fmt""sync""time"
)var w sync.WaitGroupfunc main() {w.Add(1)deadline, ctx := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))defer ctx()go func(ctx2 context.Context) {defer w.Done()for {select {case <-deadline.Done():fmt.Println("上下文取消")returndefault:fmt.Println("等待取消")}time.Sleep(1 * time.Second)}}(deadline)w.Wait()
}

WithTimeout其实与WithDealine非常相似,它的实现也只是稍微封装了一下并调用WithDeadline,和上面例子中的WithDeadline用法一样,如下:

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {return WithDeadline(parent, time.Now().Add(timeout))
}

注意:
就跟内存分配后不回收会造成内存泄漏一样,上下文也是一种资源,如果创建了但从来不取消,一样会造成上下文泄露,所以最好避免此种情况的发生。


http://www.ppmy.cn/ops/9703.html

相关文章

C#各大版本特性

总目录 C# 语法总目录 C#各大版本特性目录 C#各大版本特性C#10.0C#9.0C#8.0C#7.0C#6.0C#5.0C#4.0C#3.0C#2.0 C#各大版本特性 C#10.0 支持全局using语句&#xff1a;现在可以在整个项目中使用全局using语句&#xff0c;在所有文件中自动引用命名空间&#xff0c;不需要在每个文…

ubuntu快捷更pip源

py安装: apt-get install python3-pip终端输入: pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/

最优控制理论笔记 - 03无约束条件下的泛函极值问题

一、始端时刻t0和终端时刻tf时刻都给定的泛函极值问题 其中式子2.8为欧拉方程&#xff0c;式子2.9为横截条件。 上述推导的重要作用在于将求泛函的极值问题转化为求解欧拉方程在满足边界条件和横截条件下的定解问题。 1. 固定始端和终端 2. 自由始端和自由终端 3. 自由始端和…

《QT实用小工具·三十六》metro风格的主界面

1、概述 源码放在文章末尾 该项目实现了metro风格的主界面&#xff0c;包含访客登记&#xff0c;记录查询&#xff0c;证件扫描&#xff0c;信息打印&#xff0c;系统设置&#xff0c;系统重启等功能&#xff0c;项目demo演示如下所示&#xff1a; 源码下载

Leetcode 53. 最大子数组和

心路历程&#xff1a; 子数组的和是可以通过前面的和加上当前值递推获得&#xff0c;所以可以用动态规划解决这道题 注意的点&#xff1a; 1、这道题再获取最大值时res不能用0而需要用负无穷初始化 解法&#xff1a;动态规划 class Solution:def maxSubArray(self, nums: …

【Web】2022DASCTF X SU 三月春季挑战赛 题解(全)

目录 ezpop calc upgdstore ezpop 瞪眼看链子 fin#__destruct -> what#__toString -> fin.run() -> crow#__invoke -> fin#__call -> mix.get_flag() exp <?php class crow {public $v1;public $v2;}class fin {public $f1; }class what {public $a; }…

基于Springboot的口腔管家平台

基于SpringbootVue的口腔管家平台的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页展示 牙齿保健产品 牙齿保护小知识 后台登录页面 会员管理 病例就诊信息管理…

网络常识!!!

网络常识!!! 一:网络的发展史二:关键的概念三:IP地址四:端口号二级目录二级目录二级目录二级目录三级目录 一:网络的发展史 从游戏方面发展历程进行理解: 从单机游戏-----游戏支持局域网对战-------游戏支持广域网对战-------移动端 (1)局域网对战:在同一个网吧里,不同的游戏…