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

相关文章

Web服务器-Nginx-高并发问题

《Web服务器-Nginx-高并发问题》Nginx通过事件驱动、I/O多路复用和异步非阻塞技术高效处理高并发,结合动静分离和限流策略,提升性能与稳定性... 目录前言一、架构1. 原生多进程架构2. 事件驱动模型3. IO多路复用4. 异步非阻塞 I/O5. Nginx高并发配置实战二、动静分离1. 职责2

SpringBoot集成XXL-JOB实现任务管理全流程

《SpringBoot集成XXL-JOB实现任务管理全流程》XXL-JOB是一款轻量级分布式任务调度平台,功能丰富、界面简洁、易于扩展,本文介绍如何通过SpringBoot项目,使用RestTempl... 目录一、前言二、项目结构简述三、Maven 依赖四、Controller 代码详解五、Service

Android协程高级用法大全

《Android协程高级用法大全》这篇文章给大家介绍Android协程高级用法大全,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友跟随小编一起学习吧... 目录1️⃣ 协程作用域(CoroutineScope)与生命周期绑定Activity/Fragment 中手

Python异步编程之await与asyncio基本用法详解

《Python异步编程之await与asyncio基本用法详解》在Python中,await和asyncio是异步编程的核心工具,用于高效处理I/O密集型任务(如网络请求、文件读写、数据库操作等),接... 目录一、核心概念二、使用场景三、基本用法1. 定义协程2. 运行协程3. 并发执行多个任务四、关键

Linux系统管理与进程任务管理方式

《Linux系统管理与进程任务管理方式》本文系统讲解Linux管理核心技能,涵盖引导流程、服务控制(Systemd与GRUB2)、进程管理(前台/后台运行、工具使用)、计划任务(at/cron)及常用... 目录引言一、linux系统引导过程与服务控制1.1 系统引导的五个关键阶段1.2 GRUB2的进化优

AOP编程的基本概念与idea编辑器的配合体验过程

《AOP编程的基本概念与idea编辑器的配合体验过程》文章简要介绍了AOP基础概念,包括Before/Around通知、PointCut切入点、Advice通知体、JoinPoint连接点等,说明它们... 目录BeforeAroundAdvise — 通知PointCut — 切入点Acpect — 切面

Spring Security 前后端分离场景下的会话并发管理

《SpringSecurity前后端分离场景下的会话并发管理》本文介绍了在前后端分离架构下实现SpringSecurity会话并发管理的问题,传统Web开发中只需简单配置sessionManage... 目录背景分析传统 web 开发中的 sessionManagement 入口ConcurrentSess

Python Flask实现定时任务的不同方法详解

《PythonFlask实现定时任务的不同方法详解》在Flask中实现定时任务,最常用的方法是使用APScheduler库,本文将提供一个完整的解决方案,有需要的小伙伴可以跟随小编一起学习一下... 目录完js整实现方案代码解释1. 依赖安装2. 核心组件3. 任务类型4. 任务管理5. 持久化存储生产环境

C#异步编程ConfigureAwait的使用小结

《C#异步编程ConfigureAwait的使用小结》本文介绍了异步编程在GUI和服务器端应用的优势,详细的介绍了async和await的关键作用,通过实例解析了在UI线程正确使用await.Conf... 异步编程是并发的一种形式,它有两大好处:对于面向终端用户的GUI程序,提高了响应能力对于服务器端应

MySQL中处理数据的并发一致性的实现示例

《MySQL中处理数据的并发一致性的实现示例》在MySQL中处理数据的并发一致性是确保多个用户或应用程序同时访问和修改数据库时,不会导致数据冲突、数据丢失或数据不一致,MySQL通过事务和锁机制来管理... 目录一、事务(Transactions)1. 事务控制语句二、锁(Locks)1. 锁类型2. 锁粒