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

相关文章

golang内存对齐的项目实践

《golang内存对齐的项目实践》本文主要介绍了golang内存对齐的项目实践,内存对齐不仅有助于提高内存访问效率,还确保了与硬件接口的兼容性,是Go语言编程中不可忽视的重要优化手段,下面就来介绍一下... 目录一、结构体中的字段顺序与内存对齐二、内存对齐的原理与规则三、调整结构体字段顺序优化内存对齐四、内

使用Python在Excel中创建和取消数据分组

《使用Python在Excel中创建和取消数据分组》Excel中的分组是一种通过添加层级结构将相邻行或列组织在一起的功能,当分组完成后,用户可以通过折叠或展开数据组来简化数据视图,这篇博客将介绍如何使... 目录引言使用工具python在Excel中创建行和列分组Python在Excel中创建嵌套分组Pyt

C#多线程编程中导致死锁的常见陷阱和避免方法

《C#多线程编程中导致死锁的常见陷阱和避免方法》在C#多线程编程中,死锁(Deadlock)是一种常见的、令人头疼的错误,死锁通常发生在多个线程试图获取多个资源的锁时,导致相互等待对方释放资源,最终形... 目录引言1. 什么是死锁?死锁的典型条件:2. 导致死锁的常见原因2.1 锁的顺序问题错误示例:不同

Spring Boot 整合 ShedLock 处理定时任务重复执行的问题小结

《SpringBoot整合ShedLock处理定时任务重复执行的问题小结》ShedLock是解决分布式系统中定时任务重复执行问题的Java库,通过在数据库中加锁,确保只有一个节点在指定时间执行... 目录前言什么是 ShedLock?ShedLock 的工作原理:定时任务重复执行China编程的问题使用 Shed

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

Golang操作DuckDB实战案例分享

《Golang操作DuckDB实战案例分享》DuckDB是一个嵌入式SQL数据库引擎,它与众所周知的SQLite非常相似,但它是为olap风格的工作负载设计的,DuckDB支持各种数据类型和SQL特性... 目录DuckDB的主要优点环境准备初始化表和数据查询单行或多行错误处理和事务完整代码最后总结Duck

Golang的CSP模型简介(最新推荐)

《Golang的CSP模型简介(最新推荐)》Golang采用了CSP(CommunicatingSequentialProcesses,通信顺序进程)并发模型,通过goroutine和channe... 目录前言一、介绍1. 什么是 CSP 模型2. Goroutine3. Channel4. Channe

Python Invoke自动化任务库的使用

《PythonInvoke自动化任务库的使用》Invoke是一个强大的Python库,用于编写自动化脚本,本文就来介绍一下PythonInvoke自动化任务库的使用,具有一定的参考价值,感兴趣的可以... 目录什么是 Invoke?如何安装 Invoke?Invoke 基础1. 运行测试2. 构建文档3.

解决Cron定时任务中Pytest脚本无法发送邮件的问题

《解决Cron定时任务中Pytest脚本无法发送邮件的问题》文章探讨解决在Cron定时任务中运行Pytest脚本时邮件发送失败的问题,先优化环境变量,再检查Pytest邮件配置,接着配置文件确保SMT... 目录引言1. 环境变量优化:确保Cron任务可以正确执行解决方案:1.1. 创建一个脚本1.2. 修

Java实现任务管理器性能网络监控数据的方法详解

《Java实现任务管理器性能网络监控数据的方法详解》在现代操作系统中,任务管理器是一个非常重要的工具,用于监控和管理计算机的运行状态,包括CPU使用率、内存占用等,对于开发者和系统管理员来说,了解这些... 目录引言一、背景知识二、准备工作1. Maven依赖2. Gradle依赖三、代码实现四、代码详解五