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

文章目录

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


前言

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

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

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


一、单个任务的取消

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

package main

import (
	"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)
			return
		default:
			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 main

import (
	"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)
			return
		default:
			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() error
    Value(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 main

import (
	"context"
	"fmt"
	"time"
)

func monitor(name string, ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Printf("%v 监控退出,停止了...\n", name)
			return
		default:
			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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/643729.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

探索Python编程乐趣:制作气泡反弹小游戏

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言&#xff1a;Python编程的轻松入门 二、游戏实现原理&#xff1a;气泡反弹的逻辑 …

Linux|如何在 awk 中使用流控制语句

引言 当您从 Awk 系列一开始回顾我们迄今为止介绍的所有 Awk 示例时&#xff0c;您会注意到各个示例中的所有命令都是按顺序执行的&#xff0c;即一个接一个。但在某些情况下&#xff0c;我们可能希望根据某些条件运行一些文本过滤操作&#xff0c;这就是流程控制语句的方法。 …

【MyBatis】MyBatis解析全局配置文件源码详解

目录 一、前言 思维导图概括 二、配置文件解析过程分析 2.1 配置文件解析入口 2.2 初始化XMLConfigBuilder 2.3 XMLConfigBuilder#parse()方法&#xff1a;解析全局配置文件 2.3.1 解析properties配置 2.3.2 解析settings配置 2.3.2.1 元信息对象&#xff08;MetaClas…

结构型模式之桥接模式

文章目录 概述原理结构图代码示例 小结 概述 桥接模式(bridge pattern) 的定义是&#xff1a;将抽象部分与它的实现部分分离&#xff0c;使它们都可以独立地变化。 桥接模式用一种巧妙的方式处理多层继承存在的问题,用抽象关联来取代传统的多层继承,将类之间的静态继承关系转…

oracle数据库无法连接问题排查

查看数据库告警日志如下图。发现问题时间段&#xff0c;没有数据库服务故障报错&#xff0c;但是存在较多TNS-12535、12560、12170、00505错误&#xff1a; 通过检查问题时间段应用日志&#xff0c;也记录了Caused by:java.sql.SQLRecoverableException: IO 错误: Connection r…

从反向传播(BP)到BPTT:详细数学推导【原理理解】

从反向传播到BPTT&#xff1a;详细推导与问题解析 在本文中&#xff0c;我们将从反向传播算法开始&#xff0c;详细推导出反向传播通过时间&#xff08;Backpropagation Through Time, BPTT&#xff09;算法。重点讨论BPTT中的梯度消失和梯度爆炸问题&#xff0c;并解释如何解…

如何彻底搞懂装饰器(Decorator)设计模式?

对于任何一个软件系统而言&#xff0c;往现有对象中添加新功能是一种不可避免的实现场景&#xff0c;但这一实现过程对现有系统的影响可大可小。从架构设计上讲&#xff0c;我们也知道存在一个开闭原则&#xff08;Open-Closed Principle&#xff0c;OCP&#xff09;&#xff0…

asp.net core接入prometheus2-自定义指标

前提 了解一下asp.net core接入prometheus快速入门 https://blog.csdn.net/qq_36437991/article/details/139064138 新建.net 8空web项目 安装下面三个包 <PackageReference Include"OpenTelemetry.Exporter.Prometheus.AspNetCore" Version"1.8.0-rc.1&…

真拿AI赚到钱的人,不在朋友圈里

1 最近有张两大AI巨头对比的梗图给我看乐了&#xff0c;玩儿AI的还在做产品&#xff0c;玩儿焦虑的已经在数钱了。 这也是在做AI&#xff0c;只不过是唉声叹气的ai。 要我说&#xff0c;现在缺的根本不是AI&#xff0c;而是【有用的AI】。 恩格斯老师说过一句话&#xff1a…

Java 对接百度网盘

文章目录 前言一、创建百度网盘账号二、代码实现1. 常量类2. 工具类3. 授权码模式授权4. 文件分片上传&#xff08;可获取进度&#xff09;--方法一5. 文件下载(可获取进度)--方法一6. 获取文件列表7. 文件分片上传&#xff08;不可获取进度&#xff09;--方法二7. 文件下载&am…

算法之堆排序

堆排序是一种基于比较的排序算法&#xff0c;通过构建二叉堆&#xff08;Binary Heap&#xff09;&#xff0c;可以利用堆的性质进行高效的排序。二叉堆是一个完全二叉树&#xff0c;可以有最大堆和最小堆两种形式。在最大堆中&#xff0c;父节点的值总是大于或等于其子节点的值…

C++与Android处理16进制大端/小端数据实例(二百七十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

“大数据建模、分析、挖掘技术应用研修班”的通知!

随着2015年9月国务院发布了《关于印发促进大数据发展行动纲要的通知》&#xff0c;各类型数据呈现出了指数级增长&#xff0c;数据成了每个组织的命脉。今天所产生的数据比过去几年所产生的数据大好几个数量级&#xff0c;企业有了能够轻松访问和分析数据以提高性能的新机会&am…

夏日采摘季,视频智能监控管理方案助力智慧果园管理新体验

5月正值我国各地西瓜、杨梅、大樱桃、油桃等水果丰收的季节&#xff0c;许多地方都举办了采摘旅游活动&#xff0c;吸引了众多游客前来体验采摘乐趣。随着采摘的人流量增多&#xff0c;果园的管理工作也面临压力。 为了提升水果园采摘活动的管理效果&#xff0c;减少人工巡查成…

harbor 认证

Harbor 认证过程 Harbor以 Docker Registry v2认证为基础&#xff0c;添加上一层权限保护。1.v2 集成了一个安全认证的功能&#xff0c;将安全认证暴露给外部服务&#xff0c;让外部服务去实现2.强制用户每次Docker pull/push请求都要带一个合法的Token&#xff0c;Registry会…

基于jeecgboot-vue3的Flowable新建流程定义(一)

因为这个项目license问题无法开源&#xff0c;更多技术支持与服务请加入我的知识星球。 1、vue3版本因为流程分类是动态的&#xff0c;不再固定了&#xff0c;所以新建的时候需要选择建立哪种流程类型的流程 代码如下&#xff1a; <!-- 选择模型的流程类型对话框 -->&…

JDBCTemplate介绍

Spring JDBC Spring框架对Spring的简单封装。提供一个JDBCTemplate对象简化JDBC开发 *步骤&#xff1a; 1、导入jar包 2、创建JDBCTemplate对象。依赖于数据源DataSource *JdbcTemplate templatenew JdbcTemplate(ds); 3、调用JdbcTemplate的方法来完成CRUD的操作 *update()&…

【实战教程】使用Spring AOP和自定义注解监控接口调用

一、背景 随着项目的长期运行和迭代&#xff0c;积累的功能日益繁多&#xff0c;但并非所有功能都能得到用户的频繁使用或实际上根本无人问津。 为了提高系统性能和代码质量&#xff0c;我们往往需要对那些不常用的功能进行下线处理。 那么&#xff0c;该下线哪些功能呢&…

代码随想录-Day18

513. 找树左下角的值 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 方法一&#xff1a;深度优先搜索 class Solution {int curVal 0;int curHeight 0;public int findBottomLeftValue(TreeNode roo…

GitLens或者Git Graph在vscode中对比文件历史变化,并将历史变化同步到当前文件中

有时候我们上周改的代码&#xff0c;现在想反悔把它恢复过来&#xff0c;怎么办&#xff1f;&#xff1f;&#xff1f;很好&#xff0c;你有这个需求&#xff0c;说明你找对人了&#xff0c;那就是我们需要在vscode中安装这个插件&#xff1a;GitLens或者Git Graph&#xff0c;…