山东专业网站seo/新闻媒体发布平台
怎样才能让主goroutine等待其他goroutine?
上篇文章提到,一旦主 goroutine 中的代码执行完毕,当前的 Go 程序就会结束运行,无论其他的 goroutine 是否已经在运行了。那么,怎样才能做到等其他的 goroutine 运行完毕之后,再让主 goroutine 结束运行呢?
最简单粗暴的办法就是让主goroutine
"小睡"一会儿。
for i := 0; i < 10; i++ {
go func() {fmt.Println(i)}()
}
time.Sleep(time.Millisecond * 500)
在for语句的后边,调用了time
包的Sleep
函数,并把time.Millisecond * 500
的结果作为参数值传给了它。time.Sleep
函数的功能就是让当前的 goroutine
(在这里就是主 goroutine
)暂停运行一段时间,直到到达指定的恢复运行时间。
time.Sleep函数会在被调用时用当前的绝对时间,再加上相对时间计算出在未来的恢复运行时间。显然,一旦到达恢复运行时间,当前的 goroutine 就会从“睡眠”中醒来,并开始继续执行后边的代码。
但是,让主 goroutine“睡眠”多长时间才是合适的呢? 如果“睡眠”太短,则很可能不足以让其他的 goroutine 运行完毕,而若“睡眠”太长则纯属浪费时间,这个时间就太难把握了。
我们可以创建一个通道,它的长度应该与我们手动启用的 goroutine的数量一致。在每个手动启用的 goroutine 即将运行完毕的时候,我们都要向该通道发送一个值。
package mainimport ("fmt"//"time"
)func main() {num := 10sign := make(chan struct{}, num)for i := 0; i < num; i++ {go func() {fmt.Println(i)sign <- struct{}{}}()}// 办法1。//time.Sleep(time.Millisecond * 500)// 办法2。for j := 0; j < num; j++ {<-sign}
}
声明通道sign
的时候是以chan struct{}
作为其类型的。其中的类型字面量struct{}
有些类似于空接口类型interface{}
,它代表了既不包含任何字段也不拥有任何方法的空结构体类型。
注意,struct{}类型值的表示法只有一个,即:struct{}{}
。并且,它占用的内存空间是0
字节。确切地说,这个值在整个 Go
程序中永远都只会存在一份。虽然我们可以无数次地使用这个值字面量,但是用到的却都是同一个值。当我们仅仅把通道当作传递某种简单信号的介质的时候,用struct{}
作为其元素类型是再好不过的了。
有没有比使用通道更好的方法?如果你知道标准库中的代码包sync
的话,那么可能会想到sync.WaitGroup
类型。没错,这是一个更好的答案。
怎样让我们启用的多个goroutine按照既定的顺序运行?
首先,我们需要稍微改造一下for
语句中的那个go函数
,要让它接受一个int
类型的参数,并在调用它的时候把变量i
的值传进去。为了不改动这个go
函数中的其他代码,我们可以把它的这个参数也命名为i
。
for i := 0; i < 10; i++ {go func(i int) {fmt.Println(i)
}(i)
}
在go
语句被执行时,我们传给go函数
的参数i
会先被求值,如此就得到了当次迭代的序号。之后,无论go函数
会在什么时候执行,这个参数值都不会变。也就是说,go函数
中调用的fmt.Println
函数打印的一定会是那个当次迭代的序号。
然后,我们再改造for语句中的go函数。
for i := uint32(0); i < 10; i++ {go func(i uint32) {fn := func() {fmt.Println(i)}trigger(i, fn)}(i)
}
在go函数
中先声明了一个匿名的函数,并把它赋给了变量fn
。这个匿名函数做的事情很简单,只是调用fmt.Println
函数以打印go函数
的参数i
的值。
在这之后,我调用了一个名叫trigger
的函数,并把go函数
的参数i
和刚刚声明的变量fn
作为参数传给了它。注意,for
语句声明的局部变量i
和go函数
的参数i
的类型都变了,都由int
变为了uint32
。
再来说trigger
函数。该函数接受两个参数,一个是uint32类型的参数i
, 另一个是func()类型
的参数fn
。你应该记得,func()
代表的是既无参数声明也无结果声明的函数类型。
trigger := func(i uint32, fn func()) {for {if n := atomic.LoadUint32(&count); n == i {fn()atomic.AddUint32(&count, 1)break}time.Sleep(time.Nanosecond)}
}
trigger函数
会不断地获取一个名叫count的变量
的值,并判断该值是否与参数i
的值相同。如果相同,那么就立即调用fn
代表的函数,然后把count变量
的值加1,最后显式地退出当前的循环。否则,我们就先让当前的 goroutine“睡眠”一个纳秒再进入下一个迭代。
注意,我操作变量count的时候使用的都是原子操作。这是由于trigger函数会被多个goroutine 并发地调用,所以它用到的非本地变量count,就被多个用户级线程共用了。因此,对它的操作就产生了竞态条件(race condition),破坏了程序的并发安全性。
所以,我们总是应该对这样的操作加以保护,在sync/atomic包中声明了很多用于原子操作的函数。
另外,由于选用的原子操作函数对被操作的数值的类型有约束,所以才对count以及相关的变量和参数的类型进行了统一的变更(由int变为了uint32)。
纵观count变量、trigger函数以及改造后的for语句和go函数,要做的是,让count变量成为一个信号,它的值总是下一个可以调用打印函数的go函数的序号。
这个序号其实就是启用 goroutine 时,那个当次迭代的序号。也正因为如此,go函数实际的执行顺序才会与go语句的执行顺序完全一致。此外,这里的trigger函数实现了一种自旋(spinning)。除非发现条件已满足,否则它会不断地进行检查。
最后要说的是,因为我依然想让主 goroutine 最后一个运行完毕,所以还需要加一行代码。
trigger(10, func(){})
调用trigger函数
完全可以达到相同的效果。由于当所有手动启用的goroutine
都运行完毕之后,count
的值一定会是10
,所以我就把10
作为了第一个参数值。又由于我并不想打印这个10
,所以我把一个什么都不做的函数作为了第二个参数值。
完整代码:
package mainimport ("fmt""sync/atomic""time"
)func main() {var count uint32trigger := func(i uint32, fn func()) {for {if n := atomic.LoadUint32(&count); n == i {fn()atomic.AddUint32(&count, 1)break}time.Sleep(time.Nanosecond)}}for i := uint32(0); i < 10; i++ {go func(i uint32) {fn := func() {fmt.Println(i)}trigger(i, fn)}(i)}trigger(10, func() {})
}
文章学习自郝林老师的《Go语言36讲》
相关文章:

【golang】go语句执行规则(goroutine)(下)
怎样才能让主goroutine等待其他goroutine? 上篇文章提到,一旦主 goroutine 中的代码执行完毕,当前的 Go 程序就会结束运行,无论其他的 goroutine 是否已经在运行了。那么,怎样才能做到等其他的 goroutine 运行完毕之后…...

websocket 接收消息无法获取用户id
1.遇到问题 公司项目是基于ruoyi 框架快速搭建开发,使用多线程搜索查询,所以以用户区分任务,保证可以搜索任务和取消搜索,所以我这需要获得用户id,使用 SecurityUtils 共工工具类从请求头获取token,然后解…...

springboot通过sharding-dbc按年、月分片
目录 springboot通过sharding-dbc按年、月分片 1、引入pom依赖 2、application.yml配置 3、分片算法 4、注意事项 1、引入pom依赖 <!--shardingjdbc分片,和Druid不兼容,如果不使用sharding则需要注释--><dependency><groupId>org.…...

基于静电放电算法优化的BP神经网络(预测应用) - 附代码
基于静电放电算法优化的BP神经网络(预测应用) - 附代码 文章目录 基于静电放电算法优化的BP神经网络(预测应用) - 附代码1.数据介绍2.静电放电优化BP神经网络2.1 BP神经网络参数设置2.2 静电放电算法应用 4.测试结果:5…...

开发者插件推荐FeHelper
开发者巨好用的插件、有很多功能比如json美化、对比,二维码/解码,图片转Base64,时间戳转换等 一、下载插件 1、打开网址:FeHelper - Awesome(建议用谷歌打开); 2、选择要下载的版本,…...

【MySQL】JSON 格式字段处理
MySQL 5.7 版本后已支持 JSON 格式,这虽是 MySQL 的一小步,但可以说是程序开发的一大步,再也不用将 JSON 内容塞到 VARCHAR 类型字段了,程序设计也会变得更加灵活。网上大多只针对JSONObject 对象类型,本文也将详解 JS…...

数据库选型<1>
数据库选型 1.SQL与NoSQL1.SQL2.NoSQL 2.各种数据存储的适应场景1.MySQL 3.构建MySQL开发环境 1.SQL与NoSQL 1.SQL 关系型数据库 MySQLOracleSQL serverPostGreSQL 关系型数据库的特点 数据结构化存储在二维表中(新增JSON存储方式,也有nosql的特点)支持事务的原子…...

1.Flink源码编译
目录 1.环境版本 1.1 jdk 1.2.maven 1.3.node 1.4.scala 2.下载flink源码 3.编译源码 4.idea打开flink源码 5.运行wordcount 1.环境版本 软件地址 链接:https://pan.baidu.com/s/1ZxYydR8rBfpLCcIdaOzxVg 提取码:12xq 1.1 jdk 1.2 maven 1.…...

Linux内核数据结构 散列表
1、散列表数据结构 在Linux内核中,散列表(哈希表)使用非常广泛。本文将对其数据结构和核心函数进行分析。和散列表相关的数据结构有两个:hlist_head 和 hlist_node //hash桶的头结点 struct hlist_head {struct hlist_node *first…...

数据库系统课设——基于python+pyqt5+mysql的酒店管理系统(可直接运行)--GUI编程
几个月之前写的一个项目,通过这个项目,你能学到关于数据库的触发器知识,python的基本语法,python一些第三方库的使用,包括python如何将前后端连接起来(界面和数据),还有界面的设计等…...

《C和指针》笔记9: typedef
C语言支持一种叫作typedef的机制,它允许你为各种数据类型定义新名字。typedef声明的写法和普通的声明基本相同,只是把typedef这个关键字出现在声明的前面。例如,下面这个声明: char *ptr_to_char;把变量ptr_to_char声明为一个指向…...

《C和指针》笔记6:gets/puts/scanf/printf/getchar函数用法
本博客可以了解一些gets/puts/scanf/printf/getchar函数的基本用法。 文章目录 1. gets函数2. puts函数3. scanf函数4. printf函数5. getchar函数6. putchar函数 1. gets函数 gets函数从标准输入读取一行文本并把它存储于作为参数传递给它的数组中。一行输入由一串字符组成&a…...

智慧课堂学生行为检测评估算法
智慧课堂学生行为检测评估算法通过yolov5系列图像识别和行为分析,智慧课堂学生行为检测评估算法评估学生的表情、是否交头接耳行为、课堂参与度以及互动质量,并提供相应的反馈和建议。智慧课堂学生行为检测评估算法能够实时监测学生的上课行为࿰…...

rainbond云原生应用管理平台部署
rainbond简介 rainbond 是 一个 开源的Kubernetes 云原生应用管理平台。 Rainbond 核心100%开源,Serverless体验,不需要懂K8s也能轻松管理容器化应用,平滑无缝过渡到K8s,是国内首个支持国产化信创、适合私有部署的一体化应用管理…...

jemter连接数据json断言
文章目录 一、jmeter连接数据库1、加载JDBC驱动2、连接数据3、SQL Query的Query Type使用方法:4、Variable Name使用方法:5、Result variable name使用方法: 二、Json响应断言1、添加 》 断言 》 JSON断言2、JSON断言界面参数说明:…...

JavaFX 加载 fxml 文件
JavaFX 加载 fxml 文件主要有两种方式,第一种方式通过 FXMLLoader 类直接加载 fxml 文件,简单直接,但是有些控件目前还不知道该如何获取,所以只能显示,目前无法处理。第二种方式较为复杂,但是可以使用与 fx…...

(三)Redis——Set
SADD key value SMEMBERS 127.0.0.1:6379> SADD set aaa 1 127.0.0.1:6379> SMEMBERS set aaa 127.0.0.1:6379> SADD set aaa 0 127.0.0.1:6379> SMEMBERS set aaaSISMEMBER 判断 aaa 是否在 set 中 127.0.0.1:6379> SISMEMBER set aaa 1 127.0.0.1:6379>…...

Vue组件通信方式详解(全面版)
在Vue应用开发中,组件通信是一个重要的话题。不同的组件可能需要在不同的情况下进行数据传递和交互。Vue提供了多种方式来实现组件通信,每种方式都有其适用的场景。本文将详细介绍Vue中实现组件通信的各种方式,并为每种方式提供通俗易懂的代码…...

什么是Promise对象?它的状态有哪些?如何使用Promise处理异步操作?以及 async、await
聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ Promise对象⭐ 创建Promise对象⭐ 使用Promise处理异步操作⭐ async、await⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅…...

Android 之自定义绘制一
绘制的基本要素 onDraw(Canvas) 绘制方法 Canvas 绘制工具 Paint 调整风格 粗细等 坐标系: x y ,3D 会有z轴,x 左到右,y 上至下,与数学中y颠倒 尺寸单位: 布局中 dp ,sp ,代码中 px;dp 为了适配不同的尺寸 绘制的关键: draw(Canvas )......(关键类:Paint) Paint.ANTI_A…...

vue3 计算两个表单得到第三个表单数据
<el-formref"ruleFormRef"label-width"150px"label-suffix":":rules"rules":disabled"drawerProps.isView":model"drawerProps.rowData"><el-form-item label"云平台名称" prop"cloudId&…...

Premiere Pro软件安装包分享(附安装教程)
目录 一、软件简介 二、软件下载 一、软件简介 Adobe Premiere Pro,简称PR,是Adobe公司开发的一款非线性视频编辑软件,被广泛应用于电影、电视剧、广告、纪录片、独立电影和音乐会等影视制作领域。它被公认为是行业内的标准工具,…...

springboot设置文件上传大小,默认是1mb
问题排查和解决过程 之前做了个项目,需要用到文件上传,启动项目正常,正常上传图片也正常,但这里图片刚好都小于1M,在代码配置文件里面也写了配置,限制大小为500M,想着就没问题(测试…...

Unity 之transform.LookAt() 调整一个物体的旋转,使其朝向指定的位置
文章目录 总的介绍补充(用于摄像机跟随的场景) 总的介绍 transform.LookAt 是 Unity 引擎中 Transform 组件的一个方法,用于调整一个物体的旋转,使其朝向指定的位置。通常情况下,它被用来使一个物体(如摄像…...

linux————haproxy
一、概述 HAProxy是一个免费的负载均衡软件,可以运行于大部分主流的Linux操作系统上(CentOS、Ubuntu、Debian、OpenSUSE、Fedora、麒麟、欧拉、UOS)。 HAProxy提供了L4(TCP)和L7(HTTP)两种负载均衡能力,具备丰富的功能。HAProxy具…...

【80天学习完《深入理解计算机系统》】第十天 3.3 条件码寄存器【CF ZF SF OF】【set】
专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客,如有问题交流,欢迎评论区留言,一定尽快回复!(大家可以去看我的专栏,是所有文章的目录) 文章字体风格: 红色文字表示&#…...

使用WSL修改docker文件存储位置
按照以下说明将其重新定位到其他驱动器/目录,并保留所有现有的Docker数据。 首先,右键单击Docker Desktop图标关闭Docker桌面,然后选择退出Docker桌面,然后,打开命令提示符: wsl --list -v您应该能够看到&a…...

软件设计师学习笔记6-存储系统
1.层次化存储体系 1.1层次化存储结构 局部性原理是层次化存储结构的支持 时空局部性:刚被访问的内容,立即又被访问(eg: 循环体 ) 空间局部性:刚被访问的内容,临近的空间很快被访问(eg:数组) 1.2层次化存储结构的分类 DRAM&…...

【TI毫米波雷达笔记】CCS雷达工程内存RAM指定(DATA_SECTION,以IWR6843AOP为例)
【TI毫米波雷达笔记】CCS雷达工程内存RAM指定(DATA_SECTION,以IWR6843AOP为例) 工程建立好以后会有一个cmd文件 此文件描述的是内存map /*----------------------------------------------------------------------------*/ /* r4f_linker…...

安卓移动应用开发实训室建设方案
一 、系统概述 安卓移动应用开发作为新一代信息技术的重点和促进信息消费的核心产业,已成为我国转变信息服务业的发展新热点:成为信息通信领域发展最快、市场潜力最大的业务领域。互联网尤其是移动互联网,以其巨大的信息交换能力和快速渗透能…...