Limit Concurrency你真的会吗

简介: Limit Concurrency你真的会吗

一. 按照正常思路来一个架构设计



1. 基础知识必备


  • sync.WaitGroup
  • chan
  • go func


2. 原始消费架构模式设计


package main
import (
 "fmt"
 "math/rand"
 "sync"
 "time"
)
func main() {
 const concurrencyProcesses = 10 // 限制最大并发处理数量
 const jobCount = 100 // 有多少任务
 var wg sync.WaitGroup
 wg.Add(jobCount)
 found := make(chan int)
 limitCh := make(chan struct{}, concurrencyProcesses) // 用chan去控制并发数量
 for i := 0; i < jobCount; i++ { //处理100个job
  limitCh <- struct{}{} // 控制并发数量
  go func(val int) {
   defer func() {
    wg.Done()
    <-limitCh
   }()
   waitTime := rand.Int31n(1000)
   fmt.Println("job:", val, "wait time:", waitTime, "millisecond")
   time.Sleep(time.Duration(waitTime) * time.Millisecond)
   found <- val
  }(i)
 }
 go func() { // 等待goroutine结束并且关闭结果chan
  wg.Wait()
  close(found)
 }()
 var results []int
 for p := range found { // 从结果chan获取数据
  fmt.Println("Finished job:", p)
  results = append(results, p)
 }
 fmt.Println("result:", results)
}


大家猜猜结果是怎么样的?


3. 公布结果

640.png


为什么deadlock?


4. 究其原因


这就是Limit Concurrency问题,即限制并发问题


在读取第一个job的时候,现将空的struct放进limitCh中,这个时候limitCh中就只剩下9个可以继续处理,接着重复这个步骤继续放入job,直到10个job装满limitCh。但是当第11个job需要处理的时候,程序就直接停止在limitCh <- struct{}{}处,因为前10个goroutine不知道什么时候才能处理结束,这样第11个job它后面的代码就完全没机会执行,造成整个系统deadlock


但是如果你的job数量小于等于10,是完全看不出来任何问题的,系统可以正常运行,只有job数量大于并发数量的时候才会出这个BUG。


二. 按照BUG再设计一版



相信大家想到了一种解决方式,既然程序是卡在limitCh <- struct{}{},那么就直接将这段处理逻辑丢到go func中处理就好了。


...
    for i := 0; i < jobCount; i++ {
        go func() { //丢到go func中去处理
            limitCh <- struct{}{}
        }()
        go func(val int) {
        ...

结果:


640.png


发现100个job同时处理直到结束,并没有达到限制并发处理的要求,虽然满足最终把这些job处理完成,但是我们要的可是Limit Concurrency


三. 最佳实践,也是大家拿来就可以用的限制并发框架模式



既然要限制并发数量,那么就建立特定数量的worker,每个worker读取chan就可以了,所以

第一步就是先建立queue的通道,将所有job都放入queue中,但是以go func方式去处理,避免阻塞main程序。


第二步就是建立特定的worker数量来消化全部的job。


package main
import (
 "fmt"
 "math/rand"
 "sync"
 "time"
)
func main() {
 const concurrencyProcesses = 10 // 限制最大并发处理数量
 const jobCount = 100 // 有多少任务
 var wg sync.WaitGroup
 wg.Add(jobCount)
 found := make(chan int)
 queue := make(chan int)
 go func(queue chan<- int) { // 生产端负责生产任务到queue
  for i := 0; i < jobCount; i++ {
   queue <- i
  }
  close(queue)
 }(queue)
 for i := 0; i < concurrencyProcesses; i++ { // 当前只有10个并发 他们做的事情就是处理任务 并且将结果set in found
  go func(queue <-chan int, found chan<- int) {
   for val := range queue {
    defer wg.Done()
    waitTime := rand.Int31n(1000)
    fmt.Println("job:", val, "wait time:", waitTime, "millisecond")
    time.Sleep(time.Duration(waitTime) * time.Millisecond)
    found <- val
   }
  }(queue, found) // 传递进去 防止data race
 }
 go func() { // 等待goroutine结束并且关闭结果chan
  wg.Wait()
  close(found)
 }()
 var results []int
 for p := range found { // 从结果chan获取数据
  fmt.Println("Finished job:", p)
  results = append(results, p)
 }
 fmt.Println("result:", results)
}

可以看到这里的for循环是以concurrencyProcesses为并发数量去处理job了。10个goroutine通过内层for循环不断读取chan中的job,直到queue中没有job为止,这样生产端生产完job之后会关闭queue,那么goroutine最后收到关闭消息后退出for循环,结束goroutine。

可以看下结果:

640.png


小结



其实还有很多常见的其他限制并发处理模式,但是我喜欢用上面这种处理模式。这里有个细节需要大家注意哈,就是控制并发的时候要处理好数据竞争(data race)问题,如果处理不好,可能最后结果不一定是对的。

- END -

相关文章
|
SQL 编解码 网络安全
|
安全 Android开发 iOS开发
深入探索Android与iOS的差异:从系统架构到用户体验
在当今的智能手机市场中,Android和iOS无疑是最受欢迎的两大操作系统。本文旨在探讨这两个平台之间的主要差异,包括它们的系统架构、开发环境、安全性、以及用户体验等方面。通过对比分析,我们可以更好地理解为何不同的用户群体可能会偏好其中一个平台,以及这些偏好背后的技术原因。
|
算法 Java 数据库连接
2025 最新 Java 面试资料及详细答案整理
这是一份全面的Java面试资料整合,涵盖基础到进阶技术知识点,包括Java核心语法、面向对象编程、异常处理、集合框架、JVM原理、多线程与并发编程、网络编程等。同时深入解析主流框架如Spring、MyBatis,以及项目实战经验分享。资料结合实例辅助理解,助你高效备考,提升面试竞争力。资源地址:[点击获取](https://pan.quark.cn/s/14fcf913bae6)。
1618 6
|
人工智能 数据可视化 前端开发
蚂蚁的可视化图表 MCP 首发上线!支持超过 25 种的可视化图表生成,也支持生成路书!
蚂蚁 AntV 团队推出可视化图表 MCP 插件,支持 25+ 种图表类型,涵盖统计图、关系图及地图标注、路径地图等,用户只需输入文本即可生成炫酷图表,大幅简化数据可视化流程,提升数据展示效率。
1897 23
|
机器学习/深度学习 分布式计算 算法
Java中的机器学习模型集成与训练实践
Java中的机器学习模型集成与训练实践
|
存储 监控 Java
Sentinel 面试题及答案整理,最新面试题
Sentinel 面试题及答案整理,最新面试题
1694 0
|
SQL 存储 分布式计算
如何构建标签画像工程体系及实现方案
本文将按总分的结构进行展开:首先对标签画像的基本概念做简单的说明;其次会从业务需求的角度出发,阐述如何构建一个可用的最小标签画像系统单元;而后会以这个最小单元为基础,对部分重点模块进行扩展介绍;最后进行总结,并对文中未涉及的发展方向简要说明。
65818 1
如何构建标签画像工程体系及实现方案
|
存储 安全 Python
如何在Python中实现一个单例模式,确保在多线程环境中也是安全的?
【2月更文挑战第5天】【2月更文挑战第11篇】如何在Python中实现一个单例模式,确保在多线程环境中也是安全的?
747 1
|
消息中间件 Java Kafka
kakfa 常见错误(长期更新)
kakfa 常见错误(长期更新)
1475 1
|
芯片
深入理解AMBA总线(一)APB总线入门(上)
深入理解AMBA总线(一)APB总线入门
2086 0

热门文章

最新文章