Golang并发编程-协程goroutine任务取消(Context)

2024-05-26 02:36

本文主要是介绍Golang并发编程-协程goroutine任务取消(Context),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言
  • 一、单个任务的取消
  • 二、 所有任务取消
  • 三、Context的出现
    • Context的定义
    • Context使用
  • 总结


前言

在实际的业务种,我们可能会有这么一种场景:需要我们主动的通知某一个goroutine结束。比如我们开启一个后台goroutine一直做事情,比如监控,现在不需要了,就需要通知这个监控goroutine结束,不然它会一直跑,就泄漏了。

我们都知道一个goroutine启动后,我们是无法控制他的,大部分情况是等待它自己结束,那么如果这个goroutine是一个不会自己结束的后台goroutine呢?比如监控等,会一直运行的。

下面我们分别介绍 chan+select 方式 和 Context方式任务的取消的方法。


一、单个任务的取消

下面的代码,代表的场景是,一个监控的任务一直在执行中,通过一个无缓存信道,在接受到bool的值的时候,协程内部通过多路复用进入监控停止的逻辑,退出任务。

package mainimport ("fmt""time"
)func cancel_1(stop chan bool) {stop <- true
}func monitor(name string, stop chan bool) {for {select {case <-stop:fmt.Printf("%v 监控退出,停止了...\n", name)returndefault:fmt.Printf("%v, goroutine监控中... \n", name)time.Sleep(2 * time.Second)}}
}func main() {stop := make(chan bool)go monitor("1号", stop)time.Sleep(10 * time.Second)fmt.Println("可以了,通知监控停止")cancel_1(stop)//为了检测监控过是否停止,如果没有监控输出,就表示停止了time.Sleep(5 * time.Second)
}

代码执行结果如下:

[root@work day01]# go run context.go 
1号, goroutine监控中... 
1号, goroutine监控中... 
1号, goroutine监控中... 
1号, goroutine监控中... 
1号, goroutine监控中... 
可以了,通知监控停止
1号 监控退出,停止了...

现在,稍微改造下上面的代码,我们再增加几个goroutine。2号,3号 。。。

func main() {stop := make(chan bool)go monitor("1号", stop)go monitor("2号", stop)go monitor("3号", stop).........
}

再次执行查看结果:

[root@work day01]# go run context.go 
3号, goroutine监控中... 
1号, goroutine监控中... 
2号, goroutine监控中... 
3号, goroutine监控中... 
1号, goroutine监控中... 
2号, goroutine监控中... 
2号, goroutine监控中... 
1号, goroutine监控中... 
3号, goroutine监控中... 
1号, goroutine监控中... 
3号, goroutine监控中... 
2号, goroutine监控中... 
1号, goroutine监控中... 
2号, goroutine监控中... 
3号, goroutine监控中... 
可以了,通知监控停止
1号 监控退出,停止了...
3号, goroutine监控中... 
2号, goroutine监控中... 
2号, goroutine监控中... 
3号, goroutine监控中... 
3号, goroutine监控中... 
2号, goroutine监控中... 

发现这种方式,只停了其中的一个任务。其他两个任务还在执行。
如何保证所有的任务都停止呢,我们看下一节

二、 所有任务取消

我们改造代码如下:
新增一个cancel_2的方法,再main函数调用cancel_2。

package mainimport ("fmt""time"
)func cancel_1(stop chan bool) {stop <- true
}func cancel_2(stop chan bool) {close(stop)
}func monitor(name string, stop chan bool) {for {select {case <-stop:fmt.Printf("%v 监控退出,停止了...\n", name)returndefault:fmt.Printf("%v, goroutine监控中... \n", name)time.Sleep(2 * time.Second)}}
}func main() {stop := make(chan bool)go monitor("1号", stop)go monitor("2号", stop)go monitor("3号", stop)time.Sleep(10 * time.Second)fmt.Println("可以了,通知监控停止")cancel_2(stop)//为了检测监控过是否停止,如果没有监控输出,就表示停止了time.Sleep(5 * time.Second)}

执行结果如下:

[root@work day01]# go run context.go 
3号, goroutine监控中... 
1号, goroutine监控中... 
2号, goroutine监控中... 
2号, goroutine监控中... 
1号, goroutine监控中... 
3号, goroutine监控中... 
1号, goroutine监控中... 
2号, goroutine监控中... 
3号, goroutine监控中... 
3号, goroutine监控中... 
1号, goroutine监控中... 
2号, goroutine监控中... 
1号, goroutine监控中... 
2号, goroutine监控中... 
3号, goroutine监控中... 
可以了,通知监控停止
3号 监控退出,停止了...
2号 监控退出,停止了...
1号 监控退出,停止了...

可以看到,三个goroutine都停止了,这里我们用到了关闭通道的广播机制,所有通道接收者都会收到关闭的消息。

上面的例子,说明当我们定义一个无缓冲通道时,如果要对所有的 goroutine 进行关闭,可以使用 close 关闭通道,然后在所有的 goroutine 里不断检查通道是否关闭(前提你得约定好,该通道你只会进行 close 而不会发送其他数据,否则发送一次数据就会关闭一个goroutine,这样会不符合咱们的预期,所以最好你对这个通道再做一层封装做个限制)来决定是否结束 goroutine。

三、Context的出现

前两节,chan+select的方式,是比较优雅的结束一个goroutine的方式,不过这种方式也有局限性,如果有很多goroutine都需要控制结束怎么办呢?如果这些goroutine又衍生了其他更多的goroutine怎么办呢?如果一层层的无穷尽的goroutine呢?如下图,当取消Handl(Req1)这个节点的任务,希望取消与他关联的子任务的时候。我们再通过上面方式是不是就需要修改很多呢。所以Context的真正用途出现了。

在这里插入图片描述

Context的定义

Context,也叫上下文,它的接口定义如下:

type Context interface {Deadline() (deadline time.Time, ok bool)Done() <-chan struct{}Err() errorValue(key interface{}) interface{}
}
  • Deadline:返回的第一个值是 截止时间,到了这个时间点,Context 会自动触发 Cancel 动作。返回的第二个值是
    一个布尔值,true 表示设置了截止时间,false 表示没有设置截止时间,如果没有设置截止时间,就要手动调用 cancel 函数取消Context。
  • Done:返回一个只读的通道(只有在被cancel后才会返回),类型为 struct{}。当这个通道可读时,意味着parent
    context已经发起了取消请求,根据这个信号,开发者就可以做一些清理动作,退出goroutine。
  • Err:返回 context 被 cancel 的原因。
  • Value:返回被绑定到 Context 的值,是一个键值对,所以要通过一个Key才可以获取对应的值,这个值一般是线程安全的。

Context使用

使用Context完成

package mainimport ("context""fmt""time"
)func monitor(name string, ctx context.Context) {for {select {case <-ctx.Done():fmt.Printf("%v 监控退出,停止了...\n", name)returndefault:fmt.Printf("%v, goroutine监控中... \n", name)time.Sleep(2 * time.Second)}}
}func main() {ctx, cancel := context.WithCancel(context.Background())go monitor("1号", ctx)go monitor("2号", ctx)go monitor("3号", ctx)time.Sleep(10 * time.Second)fmt.Println("可以了,通知监控停止")cancel()//为了检测监控过是否停止,如果没有监控输出,就表示停止了time.Sleep(5 * time.Second)
}

执行结果:

[root@work day01]# go run context2.go 
1号, goroutine监控中... 
3号, goroutine监控中... 
2号, goroutine监控中... 
1号, goroutine监控中... 
3号, goroutine监控中... 
2号, goroutine监控中... 
2号, goroutine监控中... 
3号, goroutine监控中... 
1号, goroutine监控中... 
1号, goroutine监控中... 
2号, goroutine监控中... 
3号, goroutine监控中... 
2号, goroutine监控中... 
1号, goroutine监控中... 
3号, goroutine监控中... 
可以了,通知监控停止
3号 监控退出,停止了...
1号 监控退出,停止了...
2号 监控退出,停止了...

总结

  • 任务的取消,一般可以采用chan + select方式,但是任务依赖比较复杂的情况推荐使用Context
  • 通常 Context 都是做为函数的第一个参数进行传递(规范性做法),并且变量名建议统一叫 ctx
  • Context 是线程安全的,可以放心地在多个 goroutine 中使用。
  • 当你把 Context 传递给多个 goroutine 使用时,只要执行一次 cancel 操作,所有的 goroutine 就可以收到
    取消的信号
  • 当一个 Context 被 cancel 时,继承自该 Context 的所有 子 Context 都会被 cancel。

这篇关于Golang并发编程-协程goroutine任务取消(Context)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1003317

相关文章

Linux高并发场景下的网络参数调优实战指南

《Linux高并发场景下的网络参数调优实战指南》在高并发网络服务场景中,Linux内核的默认网络参数往往无法满足需求,导致性能瓶颈、连接超时甚至服务崩溃,本文基于真实案例分析,从参数解读、问题诊断到优... 目录一、问题背景:当并发连接遇上性能瓶颈1.1 案例环境1.2 初始参数分析二、深度诊断:连接状态与

Python 异步编程 asyncio简介及基本用法

《Python异步编程asyncio简介及基本用法》asyncio是Python的一个库,用于编写并发代码,使用协程、任务和Futures来处理I/O密集型和高延迟操作,本文给大家介绍Python... 目录1、asyncio是什么IO密集型任务特征2、怎么用1、基本用法2、关键字 async1、async

Django之定时任务django-crontab的实现

《Django之定时任务django-crontab的实现》Django可以使用第三方库如django-crontab来实现定时任务的调度,本文主要介绍了Django之定时任务django-cront... 目录crontab安装django-crontab注册应用定时时间格式定时时间示例设置定时任务@符号

Android实现定时任务的几种方式汇总(附源码)

《Android实现定时任务的几种方式汇总(附源码)》在Android应用中,定时任务(ScheduledTask)的需求几乎无处不在:从定时刷新数据、定时备份、定时推送通知,到夜间静默下载、循环执行... 目录一、项目介绍1. 背景与意义二、相关基础知识与系统约束三、方案一:Handler.postDel

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

springboot使用Scheduling实现动态增删启停定时任务教程

《springboot使用Scheduling实现动态增删启停定时任务教程》:本文主要介绍springboot使用Scheduling实现动态增删启停定时任务教程,具有很好的参考价值,希望对大家有... 目录1、配置定时任务需要的线程池2、创建ScheduledFuture的包装类3、注册定时任务,增加、删

Golang HashMap实现原理解析

《GolangHashMap实现原理解析》HashMap是一种基于哈希表实现的键值对存储结构,它通过哈希函数将键映射到数组的索引位置,支持高效的插入、查找和删除操作,:本文主要介绍GolangH... 目录HashMap是一种基于哈希表实现的键值对存储结构,它通过哈希函数将键映射到数组的索引位置,支持

Spring Boot 集成 Quartz并使用Cron 表达式实现定时任务

《SpringBoot集成Quartz并使用Cron表达式实现定时任务》本篇文章介绍了如何在SpringBoot中集成Quartz进行定时任务调度,并通过Cron表达式控制任务... 目录前言1. 添加 Quartz 依赖2. 创建 Quartz 任务3. 配置 Quartz 任务调度4. 启动 Sprin

Linux之计划任务和调度命令at/cron详解

《Linux之计划任务和调度命令at/cron详解》:本文主要介绍Linux之计划任务和调度命令at/cron的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux计划任务和调度命令at/cron一、计划任务二、命令{at}介绍三、命令语法及功能 :at

shell编程之函数与数组的使用详解

《shell编程之函数与数组的使用详解》:本文主要介绍shell编程之函数与数组的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录shell函数函数的用法俩个数求和系统资源监控并报警函数函数变量的作用范围函数的参数递归函数shell数组获取数组的长度读取某下的