Go 控制协程(goroutine)的并发数量
在使用协程并发处理某些任务时, 其并发数量往往因为各种因素的限制不能无限的增大. 例如网络请求、数据库查询等等。
从运行效率角度考虑,在相关服务可以负载的前提下(限制最大并发数),尽可能高的并发。
在Go语言中,可以使用一些方法来控制协程(goroutine)的并发数量,以防止并发过多导致资源耗尽或性能下降
1、使用信号量(Semaphore)
可以使用 Go 语言中的 channel 来实现简单的信号量,限制并发数量
package mainimport ("fmt""sync"
)func worker(id int, sem chan struct{}) {sem <- struct{}{} // 占用一个信号量defer func() {<-sem // 方法运行结束释放信号量}()// 执行工作任务fmt.Printf("Worker %d: Working...\n", id)
}func main() {concurrency := 3sem := make(chan struct{}, concurrency)var wg sync.WaitGroupfor i := 0; i < 10; i++ {wg.Add(1)go func(id int) {defer wg.Done()worker(id, sem)}(i)}wg.Wait()close(sem)
}
sem 是一个有缓冲的 channel,通过控制 channel 中元素的数量,实现了一个简单的信号量机制
2、使用协程池
可以创建一个固定数量的协程池,将任务分发给这些协程执行。
package mainimport ("fmt""sync"
)func worker(id int, jobs <-chan int, results chan<- int) {//jobs等待主要协程往jobs放数据for j := range jobs {fmt.Printf("协程池 %d: 协程池正在工作 %d\n", id, j)results <- j}
}func main() {const numJobs = 5 //协程要做的工作数量const numWorkers = 3 //协程池数量jobs := make(chan int, numJobs)results := make(chan int, numJobs)var wg sync.WaitGroup// 启动协程池for i := 1; i <= numWorkers; i++ {wg.Add(1)go func(id int) {defer wg.Done()worker(id, jobs, results)}(i)}// 提交任务for j := 1; j <= numJobs; j++ {jobs <- j}close(jobs)// 等待所有工作完成go func() {wg.Wait()close(results)}()// 处理结果for result := range results {fmt.Println("Result:", result)}
}
jobs 通道用于存储任务,results 通道用于存储处理结果。通过创建固定数量的工作协程,可以有效地控制并发数量。
3、使用其他包
Go 1.16 引入了 golang.org/x/sync/semaphore 包,它提供了一个更为灵活的信号量实现。
案例一
限制对外部API的并发请求
假设我们有一个外部API,它对并发请求有限制,我们希望确保不超过这个限制。我们可以使用semaphore.Weighted来控制对API的并发访问
package mainimport ("context""fmt""golang.org/x/sync/semaphore""sync""time"
)func main() {/*1、在并发量一定的情况下,通过改变允许并发请求数可以更快处理请求任务(在CPU够用的前提下)2、sem := semaphore.NewWeighted(n),参数n就是权重量3、当一个协程需要获取的单位的权重越多,运行就会慢(比如权重总量n=5个,一个协程分配了2个,跟一个协程分配1个效率是不一样的)4、信号量没有足够的可用权重的情况发生在所有已分配的权重单位都已经被占用,即信号量的当前权重计数达到了它的总容量。在这种情况下,任何尝试通过Acquire方法获取更多权重的调用都将无法立即完成,从而导致调用者(通常是goroutine)阻塞,直到其他调用者释放一些权重单位。*//*1、权权重较大的任务在资源竞争时有更高的优先级,更容易获得执行的机会2、如果当前资源足够满足高权重任务的需求,这些任务将立即执行;若资源不足,则按照权重高低顺序排队等待3、一旦任务开始执行,其完成的速度主要取决于任务自身的逻辑复杂度、所需资源以及系统的当前负载等因素,与任务在信号量中的权重无关3、高权重的任务并不会中断已经在执行的低权重任务,而是等待这些任务自行释放资源。一旦资源释放,等待队列中的高权重任务将优先被唤醒4、Acquire 方法会检查当前信号量的可用资源量是否满足请求的权重,如果满足,则立即减少信号量的资源计数并返回,允许任务继续执行。如果不满足,任务将阻塞等待,直到有足够的资源被释放*/// 记录开始时间startTime := time.Now()// 假设外部API允许的最大并发请求为5(信号量的总容量是5个权重单位)const (maxConcurrentRequests = 5)sem := semaphore.NewWeighted(maxConcurrentRequests)var wg sync.WaitGroup// 模拟对外部API的10个并发请求for i := 0; i < 10; i++ {wg.Add(1)go func(requestId int) {defer wg.Done()// 假设我们想要获取2个单位的权重if err := sem.Acquire(context.Background(), 2); err != nil {fmt.Printf("请求 %d 无法获取信号量: %v\n", requestId, err)return}defer sem.Release(2) // 请求完成后释放信号量// 模拟对API的请求处理fmt.Printf("请求 %d 开始...\n", requestId)time.Sleep(2 * time.Second) // 模拟网络延迟fmt.Printf("请求 %d 完成。\n", requestId)}(i)}wg.Wait()// 记录结束时间endTime := time.Now()// 计算并打印总耗时fmt.Printf("程序总耗时: %v\n", endTime.Sub(startTime))
}
信号量没有足够的可用权重的情况发生在所有已分配的权重单位都已经被占用,即信号量的当前权重计数达到了它的总容量。在这种情况下,任何尝试通过Acquire方法获取更多权重的调用都将无法立即完成,从而导致调用者(通常是goroutine)阻塞,直到其他调用者释放一些权重单位。
以下是一些导致信号量没有足够可用权重的具体情况:
-
信号量初始化容量较小:如果信号量的总容量设置得较小,而并发请求的数量较大,那么很快就会出现权重不足的情况。
-
长时间占用权重:如果某些goroutine长时间占用权重单位而不释放,这会导致其他goroutine无法获取到权重,即使这些goroutine只是少数。
-
权重分配不均:在某些情况下,可能存在一些goroutine占用了不成比例的权重单位,导致其他goroutine无法获取足够的权重。
-
权重释放不及时:如果goroutine因为错误或异常情况提前退出,而没有正确释放它们所占用的权重,那么这些权重单位将不会被回收到信号量中。
-
高频率的请求:在短时间内有大量goroutine请求权重,即使它们请求的权重不大,累积起来也可能超过信号量的总容量。
-
信号量权重未正确管理:如果信号量的权重管理逻辑存在缺陷,例如错误地释放了过多的权重,或者在错误的时间点释放权重,也可能导致可用权重不足。
为了避免信号量没有足够的可用权重,可以采取以下措施:
- 合理设置信号量容量:根据资源限制和并发需求合理设置信号量的总容量。
- 及时释放权重:确保在goroutine完成工作后及时释放权重。
- 使用超时:在
Acquire调用中使用超时,避免无限期地等待权重。 - 监控和日志记录:监控信号量的使用情况,并记录关键信息,以便及时发现和解决问题。
- 权重分配策略:设计合理的权重分配策略,确保权重的公平和高效分配。
通过这些措施,可以更好地管理信号量的使用,避免因权重不足导致的并发问题。
案例二
假设有一个在线视频平台,它需要处理不同分辨率的视频转码任务。由于高清视频转码比标清视频更消耗计算资源,因此平台希望设计一个系统,能够优先处理更多标清视频转码请求,同时又不完全阻塞高清视频的转码,以保持整体服务质量和资源的有效利用。
package mainimport ("fmt""golang.org/x/net/context""golang.org/x/sync/semaphore""runtime""sync""time"
)// VideoTranscodeJob 视频转码任务
type VideoTranscodeJob struct {resolution stringweight int64
}func main() {cpuCount := runtime.NumCPU()fmt.Printf("当前CPU个数%v\n", cpuCount)/*1、权权重较大的任务在资源竞争时有更高的优先级,更容易获得执行的机会2、如果当前资源足够满足高权重任务的需求,这些任务将立即执行;若资源不足,则按照权重高低顺序排队等待3、一旦任务开始执行,其完成的速度主要取决于任务自身的逻辑复杂度、所需资源以及系统的当前负载等因素,与任务在信号量中的权重无关3、高权重的任务并不会中断已经在执行的低权重任务,而是等待这些任务自行释放资源。一旦资源释放,等待队列中的高权重任务将优先被唤醒4、Acquire 方法会检查当前信号量的可用资源量是否满足请求的权重,如果满足,则立即减少信号量的资源计数并返回,允许任务继续执行。如果不满足,任务将阻塞等待,直到有足够的资源被释放*/// 初始化两个信号量,一个用于标清,一个用于高清,假设总共有8个CPU核心可用normalSem := semaphore.NewWeighted(6) // 标清任务,分配6个单位权重,因为它们消耗资源较少highDefSem := semaphore.NewWeighted(2) // 高清任务,分配2个单位权重,因为它们更消耗资源var wg sync.WaitGroup//假设有20个需要转码的视频videoJobs := []VideoTranscodeJob{{"HD", 2}, {"HD", 2}, {"SD", 1}, {"SD", 1}, {"HD", 2},{"SD", 1}, {"SD", 1}, {"HD", 2}, {"SD", 1}, {"HD", 2},{"SD", 4}, {"SD", 4}, {"HD", 2}, {"SD", 1}, {"HD", 2},{"SD", 1}, {"SD", 4}, {"HD", 2}, {"SD", 6}, {"HD", 2},}for _, job := range videoJobs {wg.Add(1)go func(job VideoTranscodeJob) {defer wg.Done()var sem *semaphore.Weightedswitch job.resolution {case "SD":sem = normalSem //分配权重大,当前为6,任务在获取执行机会上有优势,但并不直接意味着执行速度快case "HD":sem = highDefSemdefault:panic("无效的分辨率")}if err := sem.Acquire(context.Background(), job.weight); err != nil {fmt.Printf("名为 %s 视频无法获取信号量: %v\n", job.resolution, err)return}defer sem.Release(job.weight) //释放权重对应的信号量// 模拟转码任务执行fmt.Printf("转码 %s 视频 (权重: %d)...\n", job.resolution, job.weight)//通过利用VideoTranscodeJob的weight值来模拟转码时间的长短,HD用时长则设置2比SD的1大,*时间就自然长,运行就时间长time.Sleep(time.Duration(job.weight*100) * time.Millisecond) // 模拟不同分辨率视频转码所需时间fmt.Printf("------------------------%s 视频转码完成。。。\n", job.resolution)}(job)}wg.Wait()
}
标清(SD)和高清(HD),分别分配了不同的权重(1和2)。通过创建两个不同权重的信号量,我们可以控制不同类型任务的同时执行数量,从而优先保证标清视频的快速处理,同时也确保高清视频能够在不影响系统稳定性的情况下进行转码。这展示了带权重的并发控制如何帮助在资源有限的情况下优化任务调度和执行效率。
注意:对协程分配的权重单位数不能大于对应上下文semaphore.NewWeighted(n)中参数n的单位数
案例三
package mainimport ("context""fmt""sync""time""golang.org/x/sync/semaphore"
)type weightedTask struct {id intweight int64
}func main() {const (maxTotalWeight = 20 // 最大总权重)sem := semaphore.NewWeighted(maxTotalWeight)var wg sync.WaitGrouptasksCh := make(chan weightedTask, 10)// 发送任务for i := 1; i <= 10; i++ {tasksCh <- weightedTask{id: i, weight: int64(i)} // 假设任务ID即为其权重}close(tasksCh)// 启动任务处理器for task := range tasksCh {wg.Add(1)go func(task weightedTask) {defer wg.Done()if err := sem.Acquire(context.Background(), int64(task.id)); err != nil {fmt.Printf("任务 %d 无法获取信号量: %v\n", task.id, err)return}defer sem.Release(int64(task.id)) //释放// 模拟任务执行fmt.Printf("任务 %d (权重: %d) 正在运行...\n", task.id, task.weight)time.Sleep(time.Duration(task.weight*100) * time.Millisecond) // 示例中简单用时间模拟权重影响fmt.Printf("任务 %d 完成.\n", task.id)}(task)}wg.Wait()
}
总结
选择哪种方法取决于具体的应用场景和需求。使用信号量是一种简单而灵活的方法,而协程池则更适用于需要批量处理任务的情况。golang.org/x/sync/semaphore 包提供了一个标准库外的更灵活的信号量实现
相关文章:
Go 控制协程(goroutine)的并发数量
在使用协程并发处理某些任务时, 其并发数量往往因为各种因素的限制不能无限的增大. 例如网络请求、数据库查询等等。 从运行效率角度考虑,在相关服务可以负载的前提下(限制最大并发数),尽可能高的并发。 在Go语言中,…...
web安全渗透测试十大常规项(一):web渗透测试之CSRF跨站请求伪造
渗透测试之CSRF跨站请求伪造 CSRF跨站请求伪造 CSRF跨站请求伪造...
YOLOv10尝鲜测试五分钟极简配置
最近清华大学团队又推出YOLOv10,真是好家伙了。 安装: pip install supervision githttps://github.com/THU-MIG/yolov10.git下载权重:https://github.com/THU-MIG/yolov10/releases/download/v1.0/yolov10n.pt 预测: from ult…...
社交媒体数据恢复:聊天宝
请注意,本教程仅针对聊天宝应用程序,而非其他聊天软件。以下是详细的步骤: 首先,请确保您已经登录了聊天宝应用程序。如果您尚未登录,请使用您的账号登录。 在聊天宝主界面,找到您希望恢复聊天记录的对话框…...
备战秋招—模拟版图面试题来了
随着暑期的脚步逐渐临近,电子工程和集成电路设计领域的毕业生们,也将迎来了另一个求职的黄金期——秋招。我们总说机会是留给有准备的人。对于有志于投身于模拟版图设计的学子们来说,为了在众多求职者中脱颖而出,充分备战模拟版图…...
CAN总线简介
1. CAN总线概述 1.1 CAN定义与历史背景 CAN,全称为Controller Area Network,是一种基于消息广播的串行通信协议。它最初由德国Bosch公司在1983年为汽车行业开发,目的是实现汽车内部电子控制单元(ECUs)之间的可靠通信。…...
【HSQL001】HiveSQL内置函数手册总结(更新中)
1.熟悉、梳理、总结下Hive SQL相关知识体系。 2.日常研发过程中使用较少,随着时间的推移,很快就忘得一干二净,所以梳理总结下,以备日常使用参考 3.欢迎批评指正,跪谢一键三连! 文章目录 1.函数清单 1.函数清…...
Rust面试宝典第14题:旋转数组
题目 给定一个数组,将数组中的元素向右移动k个位置,其中k是非负数。要求如下: (1)尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。 (2)使用时间复杂度为O(n)和空间…...
解决SpringBoot中插入汉字变成?(一秒解决)
在这里url后面加一行配置即可&useUnicodetrue&characterEncodingUTF-8即可 解释 spring.datasource.url: 这里包含了数据库的URL,以及额外的参数如useUnicodetrue用于启用Unicode字符集支持,characterEncodingUTF-8用于指定字符编码为UTF-8&…...
5.26牛客循环结构
1002. 难点: 两层循环条件设置 思路 可以设置三个变量 代码 1003 思路: 与星号双塔差不多,在此基础上加大一点难度 每日练题5.23 (EOF用法)-CSDN博客 代码 1004 代码...
AIGC 004-T2I-adapter另外一种支持多条件组合控制的文生图方案!
AIGC 004-T2I-adapter另外一种支持多条件组合控制的文生图方案! 文章目录 0 论文工作1 论文方法2 效果 0 论文工作 T2I-Adapter 论文提出了一种名为 T2I-Adapter 的轻量级适配器模块,旨在增强文本到图像 (T2I) 扩散模型的语义理解和生成能力。 论文指出…...
详解 Cookies 和 WebStorage
Cookies 和 WebStorage Cookies 和 WebStorageCookies简要介绍操作 Cookies(document.cookie)不足之处 WebStorage简要介绍LocalStorage Vs. SessionStorage操作 WebStorage 三种数据存储方式的对比分析共性差异 REFERENCES Cookies 和 WebStorage Cook…...
BeanFactory、FactroyBean、ApplicationContext
BeanFactory Ioc容器、定义接口规范来管理spring bean的生命周期、依赖、注入,spring中有各种Ioc容器 FactroyBean 定制的工厂Bean,可以通过抽象工厂方式创建的bean,不纳入spring的生命周期、依赖、注入特性,相当于spring给第三…...
【计算机网络】HTTPS 协议原理
加密 1. 加密概念 加密就是把明文 (要传输的信息)进行一系列变换,生成密文。 解密就是把密文再进行一系列变换,还原成明文。 在这个加密和解密的过程中,往往需要⼀个或者多个中间的数据,辅助进行这个过程,这样的数…...
springboot + Vue前后端项目(第十二记)
项目实战第十二记 1.写在前面2. 整合Echarts2.1 vue安装Echarts2.2 使用Echarts2.3 EchartsController编写2.4 Home.vue编写 总结写在最后 1.写在前面 本篇主要讲解系统整合Echarts 2. 整合Echarts 2.1 vue安装Echarts npm i echarts -S2.2 使用Echarts vue中使用echarts的…...
linux 常用命令:find grep ps netstat sudo df du rm
rm 命令 删除 -r 是递归参数(recursive),用于删除目录及其内容。如果不加这个参数,rm 命令无法删除非空目录。-f 是强制参数(force),用于强制删除文件或目录,不会进行任何确认提示…...
SQLiteOpenHelper数据库帮助器
SQLiteOpenHelper数据库帮助器是Android提供的数据库辅助工具。 1、继承SQLiteOpenHelper类,需要重写onCreate和onUpgrade两个方法 案例:实现增删改查 package com.example.databases_text;import android.app.PictureInPictureParams; import androi…...
2024年5月26日 (周日) 叶子游戏新闻
资深开发者:3A游戏当前处于一种尴尬的中间地带游戏行业整体,尤其是3A游戏正处于艰难时期。尽管2023年3A游戏佳作频出,广受好评,但居高不下的游戏开发成本(传闻《漫威蜘蛛侠2》的制作成本高达3亿美元)正严重…...
STM32-10-定时器
STM32-01-认识单片机 STM32-02-基础知识 STM32-03-HAL库 STM32-04-时钟树 STM32-05-SYSTEM文件夹 STM32-06-GPIO STM32-07-外部中断 STM32-08-串口 STM32-09-IWDG和WWDG 文章目录 一、STM32 基础定时器1. 基本定时器简介2. 基本定时器框图3. 基本定时器相关寄存器4. 定时器溢出…...
今天说的什么好呢
先这样吧...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
多元隐函数 偏导公式
我们来推导隐函数 z z ( x , y ) z z(x, y) zz(x,y) 的偏导公式,给定一个隐函数关系: F ( x , y , z ( x , y ) ) 0 F(x, y, z(x, y)) 0 F(x,y,z(x,y))0 🧠 目标: 求 ∂ z ∂ x \frac{\partial z}{\partial x} ∂x∂z、 …...
