【Golang】Go语言编程思想(六):Channel,第二节,使用Channel等待Goroutine结束

使用 Channel 等待任务结束

首先回顾上一节 channel 这一概念介绍时所写的代码:

package mainimport ("fmt""time"
)func worker(id int, c chan int) {for n := range c {fmt.Printf("Worker %d received %c\n",id, n)}
}func createWorker(id int) chan<- int {c := make(chan int)go worker(id, c)return c
}func chanDemo() {var channels [10]chan<- intfor i := 0; i < 10; i++ {channels[i] = createWorker(i)}for i := 0; i < 10; i++ {channels[i] <- 'a' + i}for i := 0; i < 10; i++ {channels[i] <- 'A' + i}time.Sleep(time.Millisecond)
}func main() {chanDemo()
}

👆 上述代码的流程如下:

首先在 main 函数中找到入口,即 chanDemo。在 chanDemo 中我们使用 var 新建了一个存储 10 个接收消息的 chan int 类型的数组,并定义了 createWorker 方法来对每一个 chan<- int类型对象进行构造。(值得注意的是,createWorker 方法是一个返回值是chan<- int的函数)

在 createWorker 中,使用 go worker(id, c) 来启动协程,c 既作为返回值返回,也会作为 worker 函数的参数在启动的协程中被使用。

此时,程序有多个流程在并行执行(并发),分别是 10 个 worker 和一个 chanDemo。我们在 chanDemo 中向 chan<- int 类型的通道发送消息,当 worker 当中的 c 有消息接收到的时候,会将消息打印出来。

现在我们希望对上述代码进行进一步的优化,在 worker 函数当中,当 n 打印完毕时,我们希望通知外部,此处的输出已经完成了。过去的做法可能会通过共享内存来进行通信,但是在 Golang 当中我们可以使用 channel 来实现上述需求,即:直接通过通信来共享内存。

我们对 worker 函数进行改进,添加一个名为 done 的 chan bool 类型,用来告知外部当前是否但因完毕:

func worker(id int, c chan int, done chan bool) {for n := range c {fmt.Printf("Worker %d received %c\n",id, n)done <- true}
}

当然,在 chanDemo 函数中,需要有一个变量来接受 done 这个 chan bool 的值。

为此,我们新建一个结构,命名为 worker,并将上面同名的函数修改为 goWorker。worker 的定义如下:

type worker struct {in   chan intdone chan bool
}

相应地对 createWorker 方法进行修改:

func createWorker(id int) worker {w := worker{				// 使用花括号显式地对 worker 进行构造in:   make(chan int),done: make(chan bool),}go doWorker(id, w.in, w.done)return w
}

函数 doWorker:

func doWorker(id int, c chan int, done chan bool) {for n := range c {fmt.Printf("Worker %d received %c\n",id, n)done <- true	// 当打印执行结束时, 将 true 输入到 chan bool 的 done 当中}
}

最后的 chanDemo 函数定义如下:

func chanDemo() {var workers [10]workerfor i := 0; i < 10; i++ {workers[i] = createWorker(i)}for i := 0; i < 10; i++ {workers[i].in <- 'a' + i<-workers[i].done				// 不用使用变量来对值进行接受// 直接显式地将值写出来, 即可在函数结束之前完成 chan int 的值的打印}for i := 0; i < 10; i++ {workers[i].in <- 'A' + i<-workers[i].done}}

完整的代码如下:

package mainimport ("fmt"
)func doWorker(id int, c chan int, done chan bool) {for n := range c {fmt.Printf("Worker %d received %c\n",id, n)done <- true}
}type worker struct {in   chan intdone chan bool
}func createWorker(id int) worker {w := worker{in:   make(chan int),done: make(chan bool),}go doWorker(id, w.in, w.done)return w
}func chanDemo() {var workers [10]workerfor i := 0; i < 10; i++ {workers[i] = createWorker(i)}for i := 0; i < 10; i++ {workers[i].in <- 'a' + i<-workers[i].done}for i := 0; i < 10; i++ {workers[i].in <- 'A' + i<-workers[i].done}}func main() {chanDemo()
}

得到的输出如下:
在这里插入图片描述
一个问题在于,上述输出是顺序执行的,失去了并行的意义。

我们想要做的是,一次性将想要打印的 20 个信息发送给 10 个通道,发送所有信息之后再等待所有通道的打印结束。

一个可能的改进如下:

func chanDemo() {var workers [10]workerfor i := 0; i < 10; i++ {workers[i] = createWorker(i)}for i, worker := range workers {worker.in <- 'a' + i			// 向 channel 发送数据}for _, worker := range workers {<-worker.done					// 首先接受数据, 再发送数据}for i, worker := range workers {worker.in <- 'A' + i}for _, worker := range workers {<-worker.done}}

此时得到的结果是:
在这里插入图片描述

使用 WaitGroup

除了显式地新建一个名为 done 的 chan bool 之外,还可以使用一个名为 WaitGroup 对象来完成等操作,它定义在头文件 sync 当中。

WaitGroup 有三个主要的方法,分别是 Add、Done 和 Wait。Add 负责告知 WaitGroup 当前还有多少个任务要完成(对于我们上面的例子,新建 10 个通道,对每个通道传入两个值,则有 20 个任务);Done 在 worker 内部打印结束后执行,告知外部任务已经完成;Wait 在外部执行,表示等待协程运行完毕之后再执行下一条语句,在我们的示例中,没有下一条语句,则函数返回。

完整的示例如下:

package mainimport ("fmt""sync"
)func doWorker(id int, c chan int, wg *sync.WaitGroup) {for n := range c {fmt.Printf("Worker %d received %c\n",id, n)wg.Done()}
}type worker struct {in chan intwg *sync.WaitGroup
}func createWorker(id int, wg *sync.WaitGroup) worker {w := worker{in: make(chan int),wg: wg, // 传递的是指针, 因为 WaitGroup 只有一个, 大家共用}go doWorker(id, w.in, wg)return w
}func chanDemo() {var wg sync.WaitGroupvar workers [10]workerwg.Add(20) // 有 20 个任务for i := 0; i < 10; i++ {workers[i] = createWorker(i, &wg)}for i, worker := range workers {worker.in <- 'a' + i// Wg.Add(1)	// 这种做法也是可以的}for i, worker := range workers {worker.in <- 'A' + i}wg.Wait()}func main() {chanDemo()
}

输出如下:
在这里插入图片描述

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

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

相关文章

【Windows】【P2P】ipv6 nmap ncat 测试电信、移动、联通两个4G 5G热点ipv6地址的连通性

测试场景 一台PC在电信4G热点下&#xff0c;一台PC在电信5G热点下。 扩展测试 电信、移动、联通的ipv6 下载安装nmap Download the Free Nmap Security Scanner for Linux/Mac/Windows 安装后&#xff0c;进入目录C:\Windows\System32\WindowsPowerShell\v1.0\powershell.e…

一文掌握 OpenGL 几何着色器的使用

学习本文需要具备 OpenGL ES 编程基础,如果看起来比较费劲,可以先看入门文章 OpenGL ES 3.0 从入门到精通系统性学习教程 。 什么是几何着色器 几何着色器(Geometry Shader) OpenGL 管线中的可选着色器阶段,位于顶点着色器(Vertex Shader) 和光栅化阶段 之间。 其核心…

C—初阶调试

对你有帮助的话能否一键三连啊&#xff01;祝每个人心想事成&#xff01; 什么是Bug? 首先我们先了解一下日常口语中的“Bug”是什么 Bug可以理解为计算机程序错误&#xff0c;编程时的漏洞 调试及重要性 顾名思义&#xff0c;调试就是通过工具找出bug存在&#xff0c;找出…

Capacitor 打包后的 iOS app 无法访问 http 的内容,解决办法

Capacitor 打包后的 iOS app 无法访问 http 的内容&#xff0c;解决办法 上篇文章中说了如何使用 Capacitor 打包成 iOS app 的过程中遇到的问题 Capacitor在 xcode 打包 iOS 应用发布的时候出错。 在这之后&#xff0c;遇到了一个新问题&#xff0c; 就是它无法访问 http 的内…

LLaMA Factory+ModelScope实战——使用 Web UI 进行监督微调

LLaMA FactoryModelScope实战——使用 Web UI 进行监督微调 文章原始地址&#xff1a;https://onlyar.site/2024/01/14/NLP-LLaMA-Factory-web-tuning/ 引言 大语言模型微调一直都是一个棘手的问题&#xff0c;不仅因为需要大量的计算资源&#xff0c;而且微调的方法也很多。在…

Excel的文件导入遇到大文件时

Excel的文件导入向导如何把已导入数据排除 入起始行&#xff0c;选择从哪一行开始导入。 比如&#xff0c;前两行已经导入了&#xff0c;第二次导入的时候排除前两行&#xff0c;从第三行开始&#xff0c;就将导入起始行设置为3即可&#xff0c;且不勾选含标题行。 但遇到大文…

【C++】选择排 序算法分析与扩展

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;代码回顾&#x1f4af;选择排序的算法流程&#x1f4af;代码详解外层循环初始化最小值内层循环比较与更新元素交换 &#x1f4af;选择排序的特性时间复杂度空间复杂度稳定性…

顺序表(数据结构初阶)

文章目录 顺序表一&#xff1a;线性表1.1概念&#xff1a; 二&#xff1a;顺序表2.1概念与结构&#xff1a;2.2分类&#xff1a;2.2.1静态顺序表2.2.2动态顺序表 2.3动态顺序表的实现声明&#xff08;初始化&#xff09;检查空间容量尾插头插尾删头删查找指定位置之前插入数据指…

【Linux】磁盘结构和文件系统

文章目录 磁盘磁盘的物理结构LBA寻址法抽象管理分区化总结 磁盘 磁盘是计算机存储系统的核心部件之一&#xff0c;主要用于长期存储数据。磁盘的基本概念、物理结构和逻辑组织形式直接影响着其性能和使用效率。 下面的图片是一个磁盘&#xff1a; 磁盘打开之后的结构如下&…

NLP-中文分词

中文分词 1、中文分词研究背景及意义 和大部分西方语言不同&#xff0c;书面汉语的词语之间没有明显的空格标记&#xff0c;句子是以字串的形式出现。因此对中文进行处理的第一步就是进行自动分词&#xff0c;即将字串转变成词串。 比如“中国建筑业呈现新格局”分词后的词串…

【Golang】Go语言编程思想(六):Channel,第三节,使用Channel实现树的遍历

使用 Channel 实现树的遍历 tree 在此处简单回顾一下之前学过的二叉树遍历&#xff0c;首先新建一个名为 tree 的目录&#xff0c;并在其下对文件和子目录进行如下组织&#xff1a; 其中 node.go 存放的是 Node 的定义&#xff1a; package treeimport "fmt"type…

spring 源码分析

1 IOC 源码解析 BeanDefinition: bean的定义。里面会有beanClass、beanName、scope等属性 beanClass&#xff1a;通过Class.forName生成的Class 对象beanName&#xff1a;context.getBean(“account”)&#xff0c;acount就是beanNamescope: 作用区分单例bean、原型bean Bea…

快速搭建SpringBoot3+Vue3+ElementPlus管理系统

快速搭建SpringBoot3Vue3管理系统 前端项目搭建&#xff08;默认开发环境&#xff1a;node20,Jdk17&#xff09;创建项目并下载依赖--执行以下命令 前端项目搭建&#xff08;默认开发环境&#xff1a;node20,Jdk17&#xff09; 创建项目并下载依赖–执行以下命令 创建项目 y…

基于Hadoop大数据音乐推荐系统的设计与实现

摘 要 各种主流的音乐平台都为用户提供了的大量的音乐&#xff0c;让他们时刻都能沉浸在音乐的海洋之中。然而&#xff0c;过多的音乐往往使用户眼花缭乱&#xff0c;很难发现他们真正所需要的。一套优秀的推荐系统&#xff0c;可以很好地解决这个问题&#xff0c;既能帮助用户…

IDEA遇到EasyConnect中的网络资源无法访问的问题

IDEA遇到EasyConnect中的网络资源无法访问的问题 摘要由CSDN通过智能技术生成 点击编辑IDEA的 启动配置&#xff0c;然后在启动器下面的新增一个请求参数然后重新启动项目&#xff0c; java.net.preferIPv4Stack true IDEA就能连接到EasyConnect代理的网络服务 wanshanyu_ 关…

IP研究 | 大数据洞察黄油小熊的爆火之路

一只来自泰国的小熊在国内红成了顶流。 今年&#xff0c;黄油小熊以烘焙店“打工人”的超萌形象迅速走红&#xff0c;2个月内火遍中国的社交媒体&#xff0c;泰国门店挤满飘洋过海求合影的中国粉丝&#xff0c;根据数说故事全网大数据洞察&#xff0c;黄油小熊2024年度的线上声…

分数求和ᅟᅠ        ‌‍‎‏

分数求和 C语言代码C 代码Java代码Python代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 输入n个分数并对他们求和&#xff0c;并用最简形式表示。所谓最简形式是指&#xff1a;分子分母的最大公约数为1&#xff1b;若最终结果的分母为…

5G中的随机接入过程可以不用收RAR?

有朋友提到了一种不用接收RAR的RA过程&#xff0c;问这个是怎么回事。其实在刚刚写过的LTM cell switch篇章中就有提到&#xff0c;这里把所有相关的内容整理如下。 在RACH-less LTM场景&#xff0c;在进行LTM cell switch之前就要先知道target cell的TA信息&#xff0c;进而才…

Ubuntu安装grafana

需求背景&#xff1a;管理服务器&#xff0c;并在线预警&#xff0c;通知 需求目的&#xff1a; 及时获取服务器状态 技能要求&#xff1a; 1、ubuntu 2、grafana 3、prometheus 4、https://img-home.csdnimg.cn/images/20230724024159.png?origin_urlhttps%3A%2F%2Fimg…

vue3获取、设置元素高度

前言 在web端常见的需求场景中&#xff0c;会经常遇到table表格需要根据页面可视区域使高度自适应的情况。 傻喵(作者本人)昨天在尝试使用vue3实现这个需求时&#xff0c;看了几篇网上写的回答&#xff0c;都不太全面&#xff0c;所以干脆自己写个总结吧.(第一次写&#xff0c…