golang -- 如何让main goroutine等一等

目录

  • 引言
  • 一、sync.WaitGroup
  • 二、channel
    • 创建
    • channle操作
    • 缓冲
    • 多返回值模式
    • 单向通道

引言

在不做修饰的程序中,代码是串行执行的

   串行、并发与并行串行:事物按照一定的发展顺序并发:同一时间段执行多个任务(一边吃饭一边看电视)并行:同一时刻执行多个任务(你和你的好朋友都在学习go语言)

对于这样一段代码

func hello() {fmt.Println("hello")
}
func main() {hello()fmt.Println("world")
}

输出

hello
world

如果启动一个goroutine

func hello() {fmt.Println("hello")
}
func main() {go hello()fmt.Println("world")
}

输出

world

为什么会出现这样的结果
这是因为在创建goroutine时需要花费一定时间,在这段时间内,main goroutine是继续执行的,如果main goroutine执行完成,就不会管其他的goroutine,程序直接退出,所以不会打印出hello

如果想要“hello”也打印出来,就要想办法让main goroutine 等一等

最简单粗暴的方法就是调用time.Sleep函数,延迟结束程序

func hello() {fmt.Println("hello")
}
func main() {hello()fmt.Println("world")time.Sleep(time.Second)
}

编译执行后打印

world
hello

为什么先打印world?
同样的,创建goroutine时需要花费一定时间,在这段时间内,main goroutine继续执行,理解为因为创建goroutine时花费了时间,所以goroutine执行起来比main goroutine慢

但是使用这种方法存在一定的问题,因为不知道程序执行具体需要多长时间
如果sleep时间过长,可能会存在 一段时间内,程序没有执行任务 的情况,这样就降低了效率
如果sleep时间过短,还是有可能打印不出goroutine中的语句


要更好的解决这个问题,有三种方法可以使用

  1. sync.WaitGroup
  2. channel
  3. Context

先不说第三种,因为还不会


一、sync.WaitGroup

使用sync.WaitGroup优化上面程序的代码是

var wg sync.WaitGroup //声明等待组变量func hello() {fmt.Println("hello")wg.Done() //当前goroutine执行完毕
}func main() {wg.Add(1) //记录需要等待的goroutine数量go hello()fmt.Println("world")wg.Wait() // 等待直到所有goroutine执行完成
}

编译执行后打印

world
hello
  • sync.WaitGroup定义
type WaitGroup struct {noCopy noCopystate atomic.Uint64 // high 32 bits are counter, low 32 bits are waiter count.sema  uint32
}

不难而见,WaitGroup是一个结构体
sync包中提供了三种WaitGroup的方法

方法名功能
func (wg *WaitGroup) Add(delta int)记录要等待的协程的数量
func (wg *WaitGroup) Done()记录当前协程已执行完毕
func (wg *WaitGroup) Wait()等待子协程结束,否则阻塞

sync.WaitGroup内部可以理解为一个计数器,计数器的值可以增加和减少。
例如当我们启动了 N 个并发任务时,就将计数器值增加N;
每个任务完成时通过调用 Done 方法将计数器减1;
通过调用 Wait 来等待并发任务执行完,当计数器值为 0 时,表示所有并发任务已经完成

举一个通俗的例子,这里将计数器称为count
你室友喊你去打游戏,你给自己定了两个目标:复习二重积分和三重积分,完成这两个目标就去和室友打游戏
首先,你给自己定了两个目标(count = 2),复习完二重积分后,你还剩下一个目标没有完成(count = 1),这时候你的室友还要继续等你(wait),好了,又把三重积分复习完了(count = 0),这时候你就和室友去打游戏了

WaitGroup 通常适用于可动态调整协程数量的时候,例如事先知道协程的数量,又或者在运行过程中需要动态调整。

WaitGroup是结构体,作为函数参数传参时,应该传递指针而不是值;如果传递值,只是将WaitGroup拷贝后,对拷贝的WaitGroup进行修改,不会改变真正的WaitGroup的值,这可能会导致主协程一直阻塞等待,程序将无法正常运行


二、channel

这一部分是我在学习channel 时候的笔记加上自己的理解
参考🔗李文周的博客-Go语言基础之并发 🔗Golang 中文学习文档-并发

创建

  1. 声明
var 变量名称 chan 元素类型
chan:关键字
元素类型:是指通道中传递元素的类型
	var ch chan int //传递整型的通道var ch1 chan string  //传递string类型的通道

没有初始化之前通道是对应类型的零值

  1. 创建

用make创建

	ch1 := make(chan int)      //没有缓冲区(下面说缓冲)ch2 := make(chan int, 1)  //缓冲区为1
  1. 关闭

使用内置的 close 关闭通道

close定义:

func close(c chan<- Type)

关闭ch通道:

close(ch)

用户必须发出一个关闭通道的指令,通道才会关闭
与文件操作不同,文件在结束操作后必须关闭,但是通道不必须关闭


channle操作

通道的操作有 发送(send)、接收(receive)和关闭(close)。
发送和接收都用符号 ‘<-’

  1. 发送

ch <-:表示对一个通道写入数据

ch <- 5 // 把5发送到ch中
  1. 接收

<- ch:表示对一个通道读取数据(直接看箭头指向区分这两种操作就可以)

  • 单返回值
x := <- ch // 从ch中接收值并赋值给变量x
<-ch       // 从ch中接收值,忽略结果
  • 双返回值
value, ok := <-ch

value:通道中的值,如果被关闭返回对应的零值
ok:布尔类型的值,通道关闭时返回false,否则返回true

双返回值还可以用来判断通道是否关闭

  • 判断通道是否被关闭:
    示例:value, ok := <-ch
    value:从通道中取出的值,如果通道被关闭则返回对应类型的零值
    ok:通道ch关闭时返回false,否则返回true

  • for range 接收值
    通常用for range循环从通道中接收值
    如果通道被关闭,会 在通道内的所有制被接收完毕后 自动退出循环
    如果没有关闭,使用for range执行时会出错

	ch4 := make(chan int, 4)ch4 <- 1ch4 <- 2ch4 <- 3ch4 <- 4close(ch4)for value := range ch4 {fmt.Println(value)}}func recv(c chan int) {ret := <-cfmt.Println("接收成功", ret)
}

输出

1
2
3
4

通道的发送接收操作可以理解为一个容器
如果容器有空间,就可以把物品放进容器;
如果容器空间满了,在容器中取出物品后,容器空间又有剩余,又可以把其他物品放入容器


关闭后的通道有以下特点:

  1. 对一个关闭的通道再发送值就会导致panic
  2. 对一个关闭的通道继续接收会一直获取值直到通道为空
  3. 对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值
  4. 关闭一个已经关闭的通道会导致panic
  5. 对已经关闭的通道再执行close也会引发panic

缓冲

  • 无缓冲的通道(阻塞的通道)
	ch1 := make(chan int)  //ch1是一个无缓冲的通道ch1 <- 10fmt.Println("发送成功")

go fatal error: all goroutines are asleep - deadlock!

deadlock -- 表示程序中的goroutine都被挂起导致程序死锁了对一个无缓冲区通道执行发送操作,会发生阻塞对一个无缓冲通道执行接收操作,没有任何向通道中发送值的操作也会导致接受操作阻塞

应对阻塞通道的方法

  1. 创建goroutine
    func recv(c chan int) {ret := <-cfmt.Println("接收成功", ret)}func main(){ch2 := make(chan int)go recv(ch2)ch2 <- 10 // 发送操作fmt.Println("发送成功")close(ch2)//fmt.Println(<-ch2) -- 报错了}
这段代码的过程
case1:如果先进行发送操作,发生堵塞,直到另一个goroutine执行接收操作
case2:如果先进行接收操作,发生堵塞,直到另一个goroutine执行发送操作
  1. 使用有缓冲的通道
	ch3 := make(chan int, 1)ch3 <- 1fmt.Println("发送成功 ")x1 := <-ch3fmt.Println(x1)  //输出1ch3 <- 2close(ch3)//对关闭的通道执行接收操作,会直到取完通道中的元素num0 := len(ch3)  //len--获取通道中元素个数x2 := <-ch3num := len(ch3)fmt.Println(num0, x2, num) //输出 1 2 0
只要通道的容量大于0,那就是有缓冲的通道,通道的容量表示通道中最大能存放的元素数量。
当通道内一铀元素达到最大容量后,再向通道中执行发送操作就会阻塞(如果接收后再发送就不会了,相当于清空了)
len -- 获取通道内元素的数量
cap -- 获取通道的容量

多返回值模式

  • 判断通道是否被关闭:
    示例:value, ok := <-ch
    value:从通道中取出的值,如果通道被关闭则返回对应类型的零值
    ok:通道ch关闭时返回false,否则返回true

  • for range 接收值
    通常用for range循环从通道中接收值
    如果通道被关闭,会 在通道内的所有制被接收完毕后 自动退出循环
    如果没有关闭,使用for range执行时会出错

	ch4 := make(chan int, 4)ch4 <- 1ch4 <- 2ch4 <- 3ch4 <- 4close(ch4)for value := range ch4 {fmt.Println(value)}}func recv(c chan int) {ret := <-cfmt.Println("接收成功", ret)
}

输出

1
2
3
4

单向通道

现在有两个函数
producer 函数 返回通道,并且执行发送操作
consume r函数从通道中接收值进行计算

func Producer() chan int {ch := make(chan int) //有没有缓冲值都可以//创建一个新的goroutine执行发送数据的任务go func() {for i := 0; i < 5; i++ {ch<-i}}close(ch)}()return ch
}func Consumer(ch chan int) int {sum := 0for value := range ch {sum += value}return sum
}

上面的代码没办法阻止在接收通道中执行发送操作,同理,没办法阻止在发送通道中执行接收操作
可以 限制参数或者返回值 来限制函数

<-chan int //只接收通道,只能接收不能发送
chan <-int //只发送通道,只能发送不能接收

改写成

func Producer1() <-chan int { //发送操作ch := make(chan int) //有没有缓冲值都可以//创建一个新的goroutine执行发送数据的任务go func() {for i := 0; i < 10; i++ {//有 1 3 5 7 9if i%2 == 1 {ch <- i}}close(ch)}()return ch
}func Consumer1(ch <-chan int) int { //接收操作sum := 0for value := range ch {sum += value}return sum
}func main() {//在函数传参及任何赋值操作中全向通道(正常通道)可以转换为单向通道,但是没办法反向转换(单项通道没办法转换成全向通道)ch1 := make(chan int, 1)ch1 <- 10close(ch1)Consumer1(ch1) //在传参时将ch1转为单项通道ch2 := make(chan int, 1)ch2 <- 4           //向ch2中发送4var ch3 <-chan int //声明一个通道 只接收ch3 = ch2          //变量赋值时将ch2转换为单向通道<-ch3              //接收操作
}

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

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

相关文章

第31讲 循环缓冲区与命令解析

串口在持续接收数据时容易发生数据黏包&#xff08;先接收的数据尚未被处理&#xff0c;后面的数据已经将内存覆盖&#xff09;的情况&#xff0c;循环缓冲区的本质就是将串口接受到的数据马上拷贝到另外一块内存之中。为了避免新来的数据覆盖掉尚未处理的数据&#xff0c;一方…

UE 材质基础 第一天

课程&#xff1a;虚幻引擎【UE5】材质宝典【初学者材质基础入门系列】-北冥没有鱼啊_-稍后再看-哔哩哔哩视频 随便记录一些 黑色是0到负无穷&#xff0c;白色是1到无穷 各向异性 有点类似于高光&#xff0c;可以配合切线来使用&#xff0c;R G B 相当于 X Y Z轴&#xff0c;切…

编译原理--期末复习

本文是我学习以下博主视频所作的笔记&#xff0c;写的不够清晰&#xff0c;建议大家直接去看这些博主的视频&#xff0c;他/她们讲得非常好&#xff1a; 基础知识概念&#xff1a; 1.【【编译原理】期末复习 零基础自学】&#xff0c;资料 2.【编译原理—混子速成期末保过】&…

【DeepSeek论文精读】11. 洞察 DeepSeek-V3:扩展挑战和对 AI 架构硬件的思考

欢迎关注[【AIGC论文精读】](https://blog.csdn.net/youcans/category_12321605.html&#xff09;原创作品 【DeepSeek论文精读】1. 从 DeepSeek LLM 到 DeepSeek R1 【DeepSeek论文精读】7. DeepSeek 的发展历程与关键技术 【DeepSeek论文精读】11. 洞察 DeepSeek-V3&#xff…

宝塔面板部署前后端项目SpringBoot+Vue2

这篇博客主要用来记录宝塔部署前端后端项目的过程。因为宝塔部署有点麻烦&#xff0c;至少在我看来挺麻烦的。我还是喜欢原始的ssh连接服务器进行操作。但是公司有项目用到了宝塔&#xff0c;没办法啊&#xff0c;只能摸索记录一下。 我们需要提前准备好后端项目的jar包和前端项…

电机试验平台:创新科技推动电动机研究发展

电机试验平台是电机制造和研发过程中不可或缺的重要设备&#xff0c;其功能涵盖了电机性能测试、电机寿命测试、电机质量评估等多个方面。随着科技的不断发展和电机应用领域的日益扩大&#xff0c;对电机试验平台的要求也越来越高。本文将从现代化电机试验平台的设计与应用两个…

LangGraph 7 - Platform - Agentic RAG、监督、SQL代理、追踪、私密对话、认证、RemoteGraph、LangSmith

文章目录 代理式检索增强生成&#xff08;Agentic RAG&#xff09;安装 1、预处理文档2、创建检索器工具3、生成查询4、文档分级5、问题重写6、生成答案7、构建流程图8、运行智能RAG代理 多智能体监督系统安装配置1、创建工作代理研究代理数学代理工具 2、使用 langgraph-super…

生命之树--树形dp

1.树形dp--在dfs遍历树的同时dp&#xff0c;从上到下递归&#xff0c;到叶子是边界条件 https://www.luogu.com.cn/problem/P8625 #include<bits/stdc.h> using namespace std; #define N 100011 typedef long long ll; typedef pair<ll,int> pii; int n,c; ll …

10.8 LangChain三大模块深度实战:从模型交互到企业级Agent工具链全解析

LangChain Community 项目:Model I/O, Retrieval, Agent Tooling 关键词:LangChain Model I/O, 检索增强生成, Agent 工具链, 多路召回策略, 工具调用协议 1. Model I/O 模块:大模型交互标准化接口 Model I/O 是 LangChain 生态中连接大模型的核心模块,定义了统一的输入输…

鸿蒙OSUniApp 实现图片上传与压缩功能#三方框架 #Uniapp

UniApp 实现图片上传与压缩功能 前言 在移动应用开发中&#xff0c;图片上传是一个非常常见的需求。无论是用户头像、朋友圈图片还是商品图片&#xff0c;都需要上传到服务器。但移动设备拍摄的图片往往尺寸较大&#xff0c;直接上传会导致流量消耗过大、上传时间过长&#x…

已经装了pygame但pycharm显示没有该模块/软件包无法加载出来下载pygame

首先&#xff0c;如果你已经通过pip install pygame或者其他什么命令下载好了pygame &#xff08;可以通过pip list查看&#xff0c;有pygame说明pygame已经成功安装在当前python环境中&#xff09;。然而&#xff0c;如果你在 PyCharm 中仍然看不到 pygame&#xff0c;可能是因…

第6章 实战案例:基于 STEVAL-IDB011V1 板级 CI/CD 全流程

在前五章中,我们完成了嵌入式 CI/CD 从环境搭建、编译自动化、测试自动化、发布分发到监控回归的全技术链条。本章将以 STEVAL-IDB011V1(搭载 BlueNRG-355)评估板为实战载体,手把手演示如何在 GitLab CI(或 Jenkins)上,构建一条从 Git Push → 编译 → 测试 → 刷写 → …

系统架构设计(十四):解释器风格

概念 解释器风格是一种将程序的每个语句逐条读取并解释执行的体系结构风格。程序在运行时不会先被编译为机器码&#xff0c;而是动态地由解释器分析并执行其语义。 典型应用&#xff1a;Python 解释器、JavaScript 引擎、Bash Shell、SQL 引擎。 组成结构 解释器风格系统的…

1-机器学习的基本概念

文章目录 一、机器学习的步骤Step1 - Function with unknownStep2 - Define Loss from Training DataStep3 - Optimization 二、机器学习的改进Q1 - 线性模型有一些缺点Q2 - 重新诠释机器学习的三步Q3 - 机器学习的扩展Q4 - 过拟合问题&#xff08;Overfitting&#xff09; 一、…

SQL里where条件的顺序影响索引使用吗?

大家好&#xff0c;我是锋哥。今天分享关于【SQL里where条件的顺序影响索引使用吗&#xff1f;】面试题。希望对大家有帮助&#xff1b; SQL里where条件的顺序影响索引使用吗&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在 SQL 查询中&#xff0c;W…

计算机科技笔记: 容错计算机设计05 n模冗余系统 TMR 三模冗余系统

NMR&#xff08;N-Modular Redundancy&#xff0c;N 模冗余&#xff09;是一种通用的容错设计架构&#xff0c;通过引入 N 个冗余模块&#xff08;N ≥ 3 且为奇数&#xff09;&#xff0c;并采用多数投票机制&#xff0c;来提升系统的容错能力与可靠性。单个模块如果可靠性小于…

中级网络工程师知识点7

1.存储区城网络SAN可分为IP-SAN,FC-SAN两种&#xff0c;从部署成本和传输效率两个方面比较两种SAN&#xff0c;比较结果为FCSAN部署成本更高 2.RAID2.0技术的优势&#xff1a; &#xff08;1&#xff09;自动负载均衡&#xff0c;降低了存储系统故障率 &#xff08;2&#x…

在Ubuntu24.04中配置开源直线特征提取软件DeepLSD

在Ubuntu24.04中配置开源直线特征提取软件DeepLSD 本文提供在Ubuntu24.04中配置开源直线特征提取软件DeepLSD的基础环境配置、列出需要修改的文件内容&#xff0c;以及报错解决方案集锦。 基础的编译安装环境 python3.8.12CUDA12gcc/g 9.5&#xff08;系统自带的g-13版本太新…

Nginx+Lua 实战避坑:从模块加载失败到版本冲突的深度剖析

Nginx 集成 Lua (通常通过 ngx_http_lua_module 或 OpenResty) 为我们提供了在 Web 服务器层面实现动态逻辑的强大能力。然而,在享受其高性能和灵活性的同时,配置和使用过程中也常常会遇到各种令人头疼的问题。本文将结合实际案例,深入分析在 Nginx+Lua 环境中常见的技术问题…

Vue.js组件开发进阶

Vue.js 是一个渐进式 JavaScript 框架&#xff0c;广泛用于构建用户界面。组件是 Vue.js 的核心概念之一&#xff0c;允许开发者将 UI 拆分为独立、可复用的模块。本文将深入探讨 Vue.js 组件的开发&#xff0c;涵盖从基础到高级的各个方面。 组件的基本概念 在 Vue.js 中&am…