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

相关文章

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

golang 日志log与logrus示例详解

《golang日志log与logrus示例详解》log是Go语言标准库中一个简单的日志库,本文给大家介绍golang日志log与logrus示例详解,感兴趣的朋友一起看看吧... 目录一、Go 标准库 log 详解1. 功能特点2. 常用函数3. 示例代码4. 优势和局限二、第三方库 logrus 详解1.

Spring定时任务只执行一次的原因分析与解决方案

《Spring定时任务只执行一次的原因分析与解决方案》在使用Spring的@Scheduled定时任务时,你是否遇到过任务只执行一次,后续不再触发的情况?这种情况可能由多种原因导致,如未启用调度、线程... 目录1. 问题背景2. Spring定时任务的基本用法3. 为什么定时任务只执行一次?3.1 未启用

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

如何使用Python实现一个简单的window任务管理器

《如何使用Python实现一个简单的window任务管理器》这篇文章主要为大家详细介绍了如何使用Python实现一个简单的window任务管理器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 任务管理器效果图完整代码import tkinter as tkfrom tkinter i

Python异步编程中asyncio.gather的并发控制详解

《Python异步编程中asyncio.gather的并发控制详解》在Python异步编程生态中,asyncio.gather是并发任务调度的核心工具,本文将通过实际场景和代码示例,展示如何结合信号量... 目录一、asyncio.gather的原始行为解析二、信号量控制法:给并发装上"节流阀"三、进阶控制

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、

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

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

Java使用多线程处理未知任务数的方案介绍

《Java使用多线程处理未知任务数的方案介绍》这篇文章主要为大家详细介绍了Java如何使用多线程实现处理未知任务数,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 知道任务个数,你可以定义好线程数规则,生成线程数去跑代码说明:1.虚拟线程池:使用 Executors.newVir