缓冲channel和限制goroutine并发数

文章目录

            • 3.7.1 用作计数信号量
            • 3.7.2 使用缓存channel+sync.WaitGroup限制并发数(类似上小节)

要限制住goroutine的并发,
一定要阻塞住main的goroutine!
一定要阻塞住main的goroutine!
一定要阻塞住main的goroutine!
可以看最后一个例子。

由于带缓冲channel的运行时层实现带有缓冲区,因此对带缓冲channel的发送操作在缓冲区未满、接收操作在缓冲区非空的情况下是异步的(发送或接收无须阻塞等待)。

默认创建的都是非缓冲channel,读写都是即时阻塞。缓冲channel自带一块缓冲区,可以暂时存储数据,如果缓冲区满了,就会发生阻塞。

  • 对一个带缓冲channel,在缓冲区无数据或有数据但未满的情况下,对其进行发送操作的goroutine不会阻塞;
  • 在缓冲区已满的情况下,**对其进行发送操作的goroutine会阻塞;**在缓冲区为空的情况下,对其进行接收操作的goroutine亦会阻塞。

记住缓冲区channel 阻塞的是接收或者发送操作的goroutine

下面通过案例对比缓冲channel与非缓冲channel.

package mainimport ("fmt""time"
)func main() {//1.非缓冲通道ch1 := make(chan int)fmt.Println("非缓冲通道", len(ch1), cap(ch1)) //非缓冲通道 0 0go func() {data := <-ch1fmt.Println("获得数据", data) //获得数据 100}()ch1 <- 100time.Sleep(time.Second)fmt.Println("赋值ok", "main over...")//2.非缓冲通道ch2 := make(chan string)go sendData(ch2)for data := range ch2 {fmt.Println("\t 读取数据", data)}fmt.Println("main over...ch2")//3. 缓冲通道,缓冲区满了才会阻塞ch3 := make(chan string, 6)go sendData(ch3)for data := range ch3 {fmt.Println("ch3 \t读取数据", data)}fmt.Println("main over...ch3")
}func sendData(ch chan string) {for i := 1; i <= 3; i++ {ch <- fmt.Sprintf("data%d", i)fmt.Println("往通道放数据:", i)}defer close(ch)
}

输出:

$ go run .\main.go
非缓冲通道 0 0
获得数据 100
赋值ok main over...
往通道放数据: 1读取数据 data1读取数据 data2
往通道放数据: 2
往通道放数据: 3读取数据 data3
main over...ch2
往通道放数据: 1
往通道放数据: 2
往通道放数据: 3
ch3     读取数据 data1
ch3     读取数据 data2
ch3     读取数据 data3
main over...ch3

非缓冲channel部分的打印结果是输入数据和接收数据交替的,这说明读写都是即时阻塞。缓冲channel部分的输入数据打印完毕以后才打印接收数据,这意味着缓冲区没有满的情况下是非阻塞的。

可以使用缓冲channel模拟生产者和消费者。

无论是单收单发还是多收多发,带缓冲channel的收发性能都要好于无缓冲channel的;对于带缓冲channel而言,选择适当容量会在一定程度上提升收发性能。

3.7.1 用作计数信号量

Go并发设计的一个惯用法是将带缓冲channel用作计数信号量(counting semaphore)。

带缓冲channel中的当前数据个数代表的是当前同时处于活动状态(处理业务)的goroutine的数量,而带缓冲channel的容量(capacity)代表允许同时处于活动状态的goroutine的最大数量。一个发往带缓冲channel的发送操作表示获取一个信号量槽位,而一个来自带缓冲channel的接收操作则表示释放一个信号量槽位。

下面是一个将带缓冲channel用作计数信号量的例子:

// chapter6/sources/go-channel-case-7.go
var active = make(chan struct{}, 3)
var jobs = make(chan int, 10)func main() {go func() {for i := 0; i < 8; i++ {jobs <- (i + 1)}close(jobs)}()var wg sync.WaitGroupfor j := range jobs {wg.Add(1)go func(j int) {active <- struct{}{}log.Printf("handle job: %d\n", j)time.Sleep(2 * time.Second)<-activewg.Done()}(j)}wg.Wait()
}

上面的示例创建了一组goroutine来处理job,同一时间最多允许3个goroutine处于活动状态。为达成这一目标,示例使用了一个容量为3的带缓冲channel,active作为计数信号量,这意味着允许同时处于活动状态的最大goroutine数量为3。我们运行一下该示例:

$go run go-channel-case-7.go 
2020/02/04 09:57:02 handle job: 8
2020/02/04 09:57:02 handle job: 4
2020/02/04 09:57:02 handle job: 1
2020/02/04 09:57:04 handle job: 2
2020/02/04 09:57:04 handle job: 3
2020/02/04 09:57:04 handle job: 7
2020/02/04 09:57:06 handle job: 6
2020/02/04 09:57:06 handle job: 5

由示例运行结果中的时间戳可以看到:虽然创建了很多goroutine,但由于计数信号量的存在,同一时间处理活动状态(正在处理job)的goroutine最多为3个。

3.7.2 使用缓存channel+sync.WaitGroup限制并发数(类似上小节)
package mainimport ("fmt""runtime""sync""time"
)func main() {run([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, 3)
}func run(dataList []int, limit int) {var wg sync.WaitGroupch := make(chan struct{}, limit)for index, ele := range dataList {wg.Add(1)fmt.Printf("%+v\n", &wg)go func(ele int, index int) {fmt.Println("for index:", index)fmt.Println("nub:", runtime.NumGoroutine())ch <- struct{}{}fmt.Println("start task:", ele)time.Sleep(10 * time.Second)fmt.Println("end task:", ele)<-chwg.Done()}(ele, index)}wg.Wait()fmt.Println("111111111111111111")
}

输出:

C:\Users\Administrator\Desktop\doc\shell\docker和k8s\项目\gotnet>go run tests/main.go
&{noCopy:{} state:{_:{} _:{} v:4294967296} sema:0}
&{noCopy:{} state:{_:{} _:{} v:8589934592} sema:0}
&{noCopy:{} state:{_:{} _:{} v:12884901888} sema:0}
&{noCopy:{} state:{_:{} _:{} v:17179869184} sema:0}
&{noCopy:{} state:{_:{} _:{} v:21474836480} sema:0}
for index: 0
nub: 6
start task: 1
for index: 3
nub: 6
start task: 4
for index: 2
nub: 6
start task: 3
for index: 4
nub: 6
&{noCopy:{} state:{_:{} _:{} v:25769803776} sema:0}
for index: 1
&{noCopy:{} state:{_:{} _:{} v:30064771072} sema:0}
&{noCopy:{} state:{_:{} _:{} v:34359738368} sema:0}
nub: 7
for index: 5
nub: 9
&{noCopy:{} state:{_:{} _:{} v:38654705664} sema:0}
for index: 7
nub: 10
&{noCopy:{} state:{_:{} _:{} v:42949672960} sema:0}
for index: 8
nub: 11
&{noCopy:{} state:{_:{} _:{} v:47244640256} sema:0}
for index: 9
nub: 12
&{noCopy:{} state:{_:{} _:{} v:51539607552} sema:0}
&{noCopy:{} state:{_:{} _:{} v:55834574848} sema:0}
&{noCopy:{} state:{_:{} _:{} v:60129542144} sema:0}
for index: 13
nub: 15
for index: 6
nub: 15
for index: 10
nub: 15
for index: 12
nub: 15
for index: 11
nub: 15
end task: 3
start task: 5
end task: 1
start task: 2
end task: 4
start task: 6
end task: 2
start task: 8
end task: 6
start task: 9
end task: 5
start task: 10
end task: 10
start task: 14
end task: 9
start task: 7
end task: 8
start task: 11
end task: 11
start task: 13
end task: 7
end task: 14
start task: 12
end task: 12
end task: 13
111111111111111111

3个一组,然后最后还剩2个一组。所有完成,就结束了主进程。

但是过程可能和一般人想的不太一样,庆看如下

  • 0-10秒只出现如下信息: 可以看出来,for循环一开始就执行完了,创建了14个goroutine, 加一个本身main的goroutine. 14+1

    C:\Users\Administrator\Desktop\doc\shell\docker和k8s\项目\gotnet>go run tests/main.go
    &{noCopy:{} state:{_:{} _:{} v:4294967296} sema:0}
    &{noCopy:{} state:{_:{} _:{} v:8589934592} sema:0}
    &{noCopy:{} state:{_:{} _:{} v:12884901888} sema:0}
    &{noCopy:{} state:{_:{} _:{} v:17179869184} sema:0}
    &{noCopy:{} state:{_:{} _:{} v:21474836480} sema:0}
    for index: 0
    nub: 6
    start task: 1
    for index: 3
    nub: 6
    start task: 4
    for index: 2
    nub: 6
    start task: 3
    for index: 4
    nub: 6
    &{noCopy:{} state:{_:{} _:{} v:25769803776} sema:0}
    for index: 1
    &{noCopy:{} state:{_:{} _:{} v:30064771072} sema:0}
    &{noCopy:{} state:{_:{} _:{} v:34359738368} sema:0}
    nub: 7
    for index: 5
    nub: 9
    &{noCopy:{} state:{_:{} _:{} v:38654705664} sema:0}
    for index: 7
    nub: 10
    &{noCopy:{} state:{_:{} _:{} v:42949672960} sema:0}
    for index: 8
    nub: 11
    &{noCopy:{} state:{_:{} _:{} v:47244640256} sema:0}
    for index: 9
    nub: 12
    &{noCopy:{} state:{_:{} _:{} v:51539607552} sema:0}
    &{noCopy:{} state:{_:{} _:{} v:55834574848} sema:0}
    &{noCopy:{} state:{_:{} _:{} v:60129542144} sema:0}
    for index: 13
    nub: 15
    for index: 6
    nub: 15
    for index: 10
    nub: 15
    for index: 12
    nub: 15
    for index: 11
    nub: 15
    

    创建了这么多goroutine,但是因为channel的容量只有3,其他想要发送channel的goroutine阻塞了。

那要如何限制goroutine的数量呢?关键点,除了缓冲的channel以外,要使main的goroutine也阻塞,这时候,需要将channel的发送和接收分离。

package mainimport ("fmt""runtime""sync""time"
)func main() {run([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, 3)}func run(dataList []int, limit int) {var wg sync.WaitGroupch := make(chan struct{}, limit)for index, ele := range dataList {wg.Add(1)ch <- struct{}{} //关键代码,这行不要放在go func()里, 此时因为ch满了,main也阻塞了,不会继续for循环创建goroutine了。fmt.Printf("%+v\n", &wg)go func(ele int, index int) {fmt.Println("for index:", index)fmt.Println("nub:", runtime.NumGoroutine())fmt.Println("start task:", ele)time.Sleep(10 * time.Second)fmt.Println("end task:", ele)<-chwg.Done()}(ele, index)}wg.Wait()fmt.Println("111111111111111111")
}

关键点在于要阻塞住main,所以必须main参与了发送或者接受。利用缓冲channel的特性。

缓冲channel自带一块缓冲区,可以暂时存储数据,如果缓冲区满了,就会发生阻塞。

  • 对一个带缓冲channel,在缓冲区无数据或有数据但未满的情况下,对其进行发送操作的goroutine不会阻塞;
  • 在缓冲区已满的情况下,**对其进行发送操作的goroutine会阻塞;**在缓冲区为空的情况下,对其进行接收操作的goroutine亦会阻塞。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

尚未解决:use_python()和use_virtualenv()的使用

reticulate包为Python和R之间的互操作性提供了一套全面的工具。该包包含以下功能&#xff1a; 以多种方式从R调用Python&#xff0c;包括RMarkdown、获取Python脚本、导入Python模块以及在R会话中交互使用Python。 R和Python对象之间的转换&#xff08;例如&#xff0c;R和Pan…

2023年中国调音台产业链、产量及市场规模分析[图]

调音台是一种专业音频设备&#xff0c;用于混音、处理和控制音频信号。它通常用于音乐制作、现场演出、录音室以及广播等场景中。调音台允许用户调整不同声音来源的音频信号&#xff0c;使其在混音过程中达到理想的音质和平衡。调音台按信号出来方式可分为&#xff1a;模拟式调…

UVa10976 Fractions Again?!(分数拆分)

1、题目 2、题意 输入正整数 k k k&#xff0c;找到所有正整数 x ≥ y x \ge y x≥y&#xff0c;使得 1 k 1 x 1 y \frac{1}{k} \frac{1}{x} \frac{1}{y} k1​x1​y1​。 3、分析 既然要求找出所有的 x , y x,y x,y&#xff0c;枚举对象自然是 x , y x,y x,y了。可…

LeetCode每日一题——2558. Take Gifts From the Richest Pile

文章目录 一、题目二、题解 一、题目 2558. Take Gifts From the Richest Pile You are given an integer array gifts denoting the number of gifts in various piles. Every second, you do the following: Choose the pile with the maximum number of gifts. If there …

名词解释 MongoDB

MongoDB 是一个面向文档的数据库管理系统&#xff0c;它不使用传统的表格结构&#xff0c;而是将数据组织成类似文档的形式&#xff0c;通常使用JSON格式。 文档数据库&#xff1a;数据以文档的形式存储&#xff0c;每个文档可以包含不同的字段&#xff0c;就像一个文件可以包…

day36(http协议 服务器软件的使用 PHP的简单了解 前后端交互 ajax 同步和异步)

一.http协议 1.http1&#xff09;前后端交互&#xff1a;前端发数据给后端&#xff0c;后端获取前端数据&#xff0c;经过解析&#xff0c;返回需要的数据2&#xff09;数据如何交互&#xff1f;请求响应3&#xff09;概念&#xff1a;http&#xff08;超文本传输协议&#xf…

C# 基于腾讯云人脸核身和百度云证件识别技术相结合的 API 实现

目录 腾讯云人脸核身技术 Craneoffice.net 采用的识别方式 1、活体人脸核身(权威库)&#xff1a; 2、活体人脸比对&#xff1a; 3、照片人脸核身(权威库)&#xff1a; 调用成本 百度云身份证识别 调用成本 相关结合点 核心代码 实现调用人脸核身API的示例 实现调用身…

拓世大模型 | 立足行业所需,发力终端,缔造智能无限可能

蒸汽机的发明为人类工业革命揭开序幕&#xff0c;引领了近现代产业变革。众所周知&#xff0c;而今AI技术的革命性突破&#xff0c;站在了时代舞台的中心&#xff0c;特别是大模型的崛起&#xff0c;无疑是第四次产业革命的焦点&#xff0c;它的地位可与当年的“蒸汽机”相提并…

性能诊断工具对比+Prometheus(普罗米修斯)监控系统学习

【精选】Prometheus&#xff08;普罗米修斯&#xff09;监控系统_普罗米修斯监控_愿许浪尽天涯的博客-CSDN博客 Java 性能诊断工具 &#x1f3cd;️... Java自带的工具 JConsoleJVisualVMjmapjstackjcmd单机图形化诊断工具 YourKitJProfilerVisualVMArthas分布式诊断工具 Zipk…

Jenkins发布windows服务器jar

一、背景 之前是linux服务器部署的项目&#xff0c;现在转为windows服务器部署。jenkins和git都已经部署好了。所以本文只会重点讲Jenkins调用windows服务器bat命令及bat命令的编写概况。 二、详情操作 1、Jenkins调用windows bat文件 首先在Jenkins项目配置"高级项目选…

【git命令】删除分支

1. 删除本地分支 使用git branch -d命令删除本地分支 git branch -d branch_name其中&#xff0c;branch_name是分支名。如果有未合并的更改&#xff0c;Git会阻止你删除分支。 使用git branch -D命令强制删除本地分支 git branch -D branch_name这个命令会强制删除分支&am…

JAVA排序

目录 再看各种排序前我们先了解一下什么叫 稳定性 插入排序&#xff1a; 希尔排序:(插入排序的优化) 测试插入排序和希尔排序(插入排序的优化)排序时间对比 选择排序: 选择排序的优化: 正确的 选择排序优化 快速排序(挖坑法:未优化) 快速排序的优化1 快速排序优化2 优化快速排序…

mysql4

创建表并插入数据&#xff1a; 字段名 数据类型 主键 外键 非空 唯一 自增 id INT 是 否 是 是 否 primary key name VARCHAR(50) 否 否 是 否 否 not null glass VARCHAR(50) 否 否 是 否 否 not nullsch 表内容 id name glass 1 xiaommg glass 1 2 xiaojun …

3.无重复字符的最长子串

​题目来源&#xff1a; leetcode题目&#xff0c;网址&#xff1a;3. 无重复字符的最长子串 - 力扣&#xff08;LeetCode&#xff09; 解题思路&#xff1a; 滑动窗口。使用哈希集合来记录窗口中的字符。当窗口右边界不为字符串右边界时&#xff0c;窗口右边界右移一位&#…

小型k8s

参考&#xff1a; 用于本地实验的小型 Kubernetes&#xff1a;k0s、MicroK8s、kind、k3s 和 Minikube - 知乎 https://www.cnblogs.com/jesse123/p/15559614.html K3s vs K8s&#xff1a;轻量级和全功能的对决_运行_部署_容器

server2012 通过防火墙开启局域网内限定IP进行远程桌面连接

我这里需要被远程桌面的电脑系统版本为windows server2012 1、打开允许远程连接设置 2、开启防火墙 3、设置允许“远程桌面应用”通过防火墙 勾选”远程桌面“ 3、入站规则设置 高级设置→入站规则→远程桌面-用户模式(TCP-In) 进入远程桌面属性的作用域——>远程IP地址—…

实体店做商城小程序如何

互联网电商深入各个行业&#xff0c;传统线下店商家无论产品销售还是服务业&#xff0c;仅靠以往的经营模式&#xff0c;很难拓展到客户&#xff0c;老客流失严重&#xff0c;同时渠道单一&#xff0c;无法实现外地客户购物及线上客户赋能等。 入驻第三方平台有优势但也有不足…

聊聊springboot的TomcatMetricsBinder

序 本文主要研究一下springboot的TomcatMetricsBinder TomcatMetricsAutoConfiguration org/springframework/boot/actuate/autoconfigure/metrics/web/tomcat/TomcatMetricsAutoConfiguration.java Configuration(proxyBeanMethods false) ConditionalOnWebApplication C…

pinia全局状态管理

使用&#xff1a;结合vue2、vue3使用 优点&#xff1a;相比vuex&#xff1a;提供组合式和选项式的写法&#xff0c;支持ts类型推断&#xff0c;提供更简洁的状态管理&#xff08;去除mutation&#xff09; 注意点&#xff1a; &#xff08;1&#xff09;使用store时不要结构&am…

Spring Web MVC入门

一&#xff1a;了解Spring Web MVC (1)关于Java开发 &#x1f31f;Java开发大多数场景是业务开发 比如说京东的业务就是电商卖货、今日头条的业务就推送新闻&#xff1b;快手的业务就是短视频推荐 (2)Spring Web MVC的简单理解 &#x1f497;Spring Web MVC&#xff1a;如何使…