Golang 并发 Cond条件变量

Golang 并发 Cond条件变量

背景

编写代码过程中, 通常有主协程和多个子协程进行协作的过程,比如通过 WaitGroup 可以实现当所有子协程完成之后, 主协程再继续执行。

如上的场景是主协程等待子协程达到某个状态再继续运行。 但是反过来怎么操作呢,要求一组子协程等待主协达到某个状态时才继续运行。这个时候就需要用到 Cond 了

简介

Cond 是和某个条件相关,在条件还没有满足的时候,所有等待这个条件的协程都会被阻塞住,只有这个条件满足的时候,等待的协程才可能继续进行下去。Cond 在初始化的时候,需要关联一个 Locker 接口的实例,一般会使用 Mutex 或者 RWMutex。Cond 关联的 Locker 实例可以通过 c.L 访问,它内部维护着一个先入先出的等待队列。

Cond 分别有三个方法如下所示:

Wait

会把当前协程放入Cond的等待队列中并阻塞,直到被Signal或者Broadcast方法从等待队列中移除并唤醒,用于子协程阻塞。

Signal

主协程唤醒等待队列中的一个子协程,先唤醒最先阻塞的子协程,被唤醒的子协程继续执行。

Broadcast

主协程唤醒等待队列中的全部协程,所有子协程继续执行。

注意:调用 SignalBroadcast 方法,不强求持有c.L的锁,调用Wait方法是必须要持有c.L的锁。

Signal的使用场景

大家都去医院先排队,然后等待叫号,先排队的先叫号。这次模拟有5个病人,分别先排队。 然后护士根据排队先后来叫号;
具体场景是,5个病人在三秒中之内分别排号,护士今天要叫5个号,一秒叫一个,叫完5个号就结束了
代码如下:

func TestCondSignal(t *testing.T) {c := sync.NewCond(&sync.Mutex{})num := 0// 当前叫号是几号hand_num := 0for i := 0; i < 5; i++ {go func(i int) {// 分别在不同时间排队time.Sleep(time.Second * time.Duration(rand.Int63n(10)))c.L.Lock()num++// 当前取得号。cur := numfmt.Printf("%s  %d 号病人取到了 %d 号\n", time.Now().Format("2006-01-02 15:04:05"), i, cur)// 取到号了,等待叫号c.Wait()fmt.Printf("%s  %d 号病人排队号是 %d 号,被叫号了\n", time.Now().Format("2006-01-02 15:04:05"), i, cur)hand_num = curc.L.Unlock()}(i)}// 都叫号了for hand_num != 5 {// 叫号c.Signal()time.Sleep(time.Second * 1)}time.Sleep(time.Second * 10)
}

代码输出:

=== RUN   TestCondSignal
2024-02-06 13:49:55  0 号病人取到了 1 号
2024-02-06 13:49:56  4 号病人取到了 2 号
2024-02-06 13:49:56  3 号病人取到了 3 号
2024-02-06 13:49:56  0 号病人排队号是 1 号,被叫号了
2024-02-06 13:49:56  1 号病人取到了 4 号
2024-02-06 13:49:57  4 号病人排队号是 2 号,被叫号了
2024-02-06 13:49:58  3 号病人排队号是 3 号,被叫号了
2024-02-06 13:49:59  1 号病人排队号是 4 号,被叫号了
2024-02-06 13:50:02  2 号病人取到了 5 号
2024-02-06 13:50:02  2 号病人排队号是 5 号,被叫号了
--- PASS: TestCondSignal (18.09s)
PASS

结果表明,5个病人,分别在三秒钟内先后取号, 然后护士每过一秒钟按照排队的先后顺序叫一个号(叫号的过程依然有病人取号),先取号的被先叫号。
此场景中,5个病人相当于5个协程, 主协程反复使用Signal() 按照顺序一个个唤醒阻塞的子协程。

Broadcast的使用场景

场景为如下: 运动员跑步比赛,要求8秒内全部运动员准备好,然后等待教练发令, 教练10秒后发令,所有运动员在发令后开始跑。

func TestBroadcast(t *testing.T) {c := sync.NewCond(&sync.Mutex{})for i := 0; i < 10; i++ {go func(i int) {// 随机一个8秒内的准备时间time.Sleep(time.Second * time.Duration(rand.Int63n(8)))fmt.Printf("%s 运动员%d已准备就绪\n", time.Now().Format("2006-01-02 15:04:05"), i)c.L.Lock()// 准备完毕,等待教练发令c.Wait()c.L.Unlock()fmt.Printf("%s 运动员%d开跑\n", time.Now().Format("2006-01-02 15:04:05"), i)}(i)}// 主协程等待10秒后发令time.Sleep(time.Second * 10)fmt.Printf("%s 教练发令。\n", time.Now().Format("2006-01-02 15:04:05"))// 教练发令。通知所有运动员开始跑步, 即唤起之前 wait()的所有协程c.Broadcast()// 等待跑步time.Sleep(time.Second * 5)
}

代码输出如下

=== RUN   TestBroadcast
2024-02-06 13:56:57 运动员4已准备就绪
2024-02-06 13:56:57 运动员7已准备就绪
2024-02-06 13:56:58 运动员8已准备就绪
2024-02-06 13:56:58 运动员3已准备就绪
2024-02-06 13:56:59 运动员9已准备就绪
2024-02-06 13:57:00 运动员2已准备就绪
2024-02-06 13:57:01 运动员5已准备就绪
2024-02-06 13:57:02 运动员1已准备就绪
2024-02-06 13:57:03 运动员6已准备就绪
2024-02-06 13:57:04 运动员0已准备就绪
2024-02-06 13:57:07 教练发令。
2024-02-06 13:57:07 运动员0开跑
2024-02-06 13:57:07 运动员9开跑
2024-02-06 13:57:07 运动员8开跑
2024-02-06 13:57:07 运动员3开跑
2024-02-06 13:57:07 运动员4开跑
2024-02-06 13:57:07 运动员5开跑
2024-02-06 13:57:07 运动员2开跑
2024-02-06 13:57:07 运动员1开跑
2024-02-06 13:57:07 运动员6开跑
2024-02-06 13:57:07 运动员7开跑
--- PASS: TestBroadcast (15.01s)

如结果所示, 10个运动员在8秒内分别准备好,等待教练发令后,同时开跑。
此场景中,10个运动员相当于10个协程, 同时等待主协程的命令,使用Broadcast() 唤醒所有阻塞的子协程。

注意事项

使用 Cond,最容易踩的坑就是调用 Wait() 方法之前,调用者没有持有锁或没有检查辅助条件。在如上示例代码中,假如把调用 Wait() 方法前后的加锁和释放锁的代码注释掉,运行代码会 导致程序 panic 。原因是调用 Wait 方法 ,会先把调用者放入等待队列中,然后释放锁。此时如果在未持有锁时调用释放锁的方法,就会 导致程序 panic

Wait方法的使用

  1. Wait会自动释放c.L锁,并挂起调用者的goroutine,之后恢复执行
  2. Wait会在返回时对c.L加锁
  3. 除非被Broadcast或Signal唤醒,否则Wait不会返回
  4. 由于Wait第一次恢复是,c.L并没有加锁,所以当Wait返回时,调用者通常不能假设条件为真
  5. 简单来说,只要想使用condition就必须加锁

参考

https://www.jb51.net/article/277047.htm

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

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

相关文章

Xilinx FPGA——在线升级

同以前单片机在线升级的做法一样&#xff0c;本质就是通信Flash操作跳转。 一、通信驱动 我使用的是UDP有线传输&#xff0c; 二、Flash芯片驱动 规划Flash芯片的区域&#xff0c;一般bootloader放在起始位置&#xff0c;APP放在bootloader之后的空白区域。 2.1 Flash擦除 我…

Java算法练习6

Java算法练习6 1.15 [506. 相对名次](https://leetcode.cn/problems/relative-ranks/)1.16 [215. 数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/)1.17 [23. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/)…

【大模型上下文长度扩展】MedGPT:解决遗忘 + 永久记忆 + 无限上下文

MedGPT&#xff1a;解决遗忘 永久记忆 无限上下文 问题&#xff1a;如何提升语言模型在长对话中的记忆和处理能力&#xff1f;子问题1&#xff1a;有限上下文窗口的限制子问题2&#xff1a;复杂文档处理的挑战子问题3&#xff1a;长期记忆的维护子问题4&#xff1a;即时信息检…

javaEE - 20( 18000字 Tomcat 和 HTTP 协议入门 -1)

一&#xff1a; HTTP 协议 1.1. HTTP 是什么 HTTP (全称为 “超文本传输协议”) 是一种应用非常广泛的 应用层协议. HTTP 诞生与1991年. 目前已经发展为最主流使用的一种应用层协议. 最新的 HTTP 3 版本也正在完善中, 目前 Google / Facebook 等公司的产品已经支持了. HTT…

美赛结束后,还可以转学术论文发表!

美赛论文转学术论文 写在前面学术论文发表的意义论文发表指导 写在前面 2024年美赛已经顺利结束了&#xff0c;美赛虽然画上了句号&#xff0c;但大家的科研竞赛之路才刚刚开始。 大家都在赛事中完成了一份具有发表基础的文章&#xff0c;想要这篇文章价值最大化&#xff0c;…

2.8作业

程序代码&#xff1a; CCgcc EXEhello OBJS$(patsubst %.c,%.o,$(wildcard *.c)) CFLAGS-c -oall:$(EXE)$(EXE):$(OBJS)$(CC) $^ -o $%.o:%.c$(CC) $(CFLAGS) $ $^.PHONY:cleanclean:rm $(OBJS) $(EXE) 程序代码&#xff1a; #include<stdio.h> #include<string.h&…

机器学习--K-近邻算法常见的几种距离算法详解

文章目录 距离度量1 欧式距离(Euclidean Distance)2 曼哈顿距离(Manhattan Distance)3 切比雪夫距离 (Chebyshev Distance)4 闵可夫斯基距离(Minkowski Distance)5 标准化欧氏距离 (Standardized EuclideanDistance)6 余弦距离(Cosine Distance)7 汉明距离(Hamming Distance)【…

nodejs将console.log保存到log.txt文档中(electron工具)

txtConsole.js const { app } require(electron); const fs require(fs); const moment require(moment); const mainData require(./mainData);//electron 软件根目录 const rootPath path.dirname(app.getPath(exe));const txtConsole {log(p1 , p2 , p3 , p4 , p…

MySQL篇----第七篇

系列文章目录 文章目录 系列文章目录前言一、水平分区二、分库分表之后,id 主键如何处理三、存储过程(特定功能的 SQL 语句集)前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你…

从零开始手写mmo游戏从框架到爆炸(七)— 消息封装

导航&#xff1a;从零开始手写mmo游戏从框架到爆炸&#xff08;零&#xff09;—— 导航-CSDN博客 上一篇&#xff0c;我们初步把消息handler 注册到了服务中&#xff0c;在进行后续工作之前我们需要再做一些准备工作。 第一&#xff1a;把之前自己管理的bean放到spring中…

c++设计模式之装饰器模式

作用 为现有类增加功能 案例说明 class Car { public:virtual void show()0; };class Bmw:public Car { public:void show(){cout<<"宝马汽车>>"<<endl;} };class Audi:public Car { public:void show(){cout<<"奥迪汽车>>&q…

三、设计模式相关理论总结

一、面向对象编程 1.1 概述 简称Object Oriented Program(OOP)&#xff0c;指以类或对象作为基础组织单元&#xff0c;遵循封装、继承、多态以及抽象等特性&#xff0c;进行编程。其中面向对象不一定遵循封装、继承、封装和多态等特性&#xff0c;只是前人总结的套路规范&…

嵌入式学习之Linux入门篇笔记——10,Linux连接档概念

配套视频学习链接&#xff1a;http://【【北京迅为】嵌入式学习之Linux入门篇】 https://www.bilibili.com/video/BV1M7411m7wT/?p4&share_sourcecopy_web&vd_sourcea0ef2c4953d33a9260910aaea45eaec8 目录 1.Linux 下的连接档种类 2.什么是 inode&#xff1f; 3.什…

下一个排列_题解

【题解提供者】史青山 解法 思路 此题属于找规律题&#xff0c;我们可以把一个序列的全排列写出来&#xff0c;然后对比找规律&#xff0c;比如序列 1 2 3 4 5&#xff0c;全排列如下&#xff1a; 1 2 3 4 5 1 2 3 5 4 1 2 4 3 5 1 2 4 5 3 1 2 5 3 4 1 2 5 4 3 1 3 2 4 5 1…

ssh使用密钥远程连接问题排查

ssh使用密钥远程连接时&#xff0c;会因为各项原因导致无法连接 本文以本机(windows), 连接目标机(Ubuntu) 为例&#xff0c; 列出应该逐项排查的问题&#xff0c; 确保能够正确连接 1. 确保两机器能够相互ping通 windows和linux都有防火墙&#xff0c; 双方ping不同大概率是因…

7、7 个适合初学者的项目,让您开始使用 ChatGPT

7 个适合初学者的项目,让您开始使用 ChatGPT 在当今世界释放人工智能的力量。 在技术以前所未有的速度发展的时代,人工智能?—?或朋友🤓的人工智能脱颖而出,成为最具变革性的力量之一。 从自动化日常任务到预测复杂模式,人工智能正在重塑行业并重新定义可能性。 当我…

Unity2D 学习笔记 0.Unity需要记住的常用知识

Unity2D 学习笔记 0.Unity需要记住的常用知识 前言调整Project SettingTilemap相关&#xff08;创建地图块&#xff09;C#脚本相关程序运行函数private void Awake()void Start()void Update() Collider2D碰撞检测private void OnTriggerStay2D(Collider2D player)private void…

【Algorithms 4】算法(第4版)学习笔记 06 - 2.3 快速排序

文章目录 前言参考目录学习笔记1&#xff1a;基本算法1.1&#xff1a;快速排序 demo 演示1.2&#xff1a;快速排序切分代码实现1.3&#xff1a;实现细节1.4&#xff1a;案例分析1.4.1&#xff1a;最佳案例1.4.2&#xff1a;最坏案例1.4.3&#xff1a;平均案例分析1.5&#xff1…

【MySQL】学习和总结DCL的权限控制

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-Bl9kYeLf8GfpdQgL {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

消息队列MQ 介绍

&#x1f47d;System.out.println(“&#x1f44b;&#x1f3fc;嗨&#xff0c;大家好&#xff0c;我是代码不会敲的小符&#xff0c;双非大四&#xff0c;Java实习中…”); &#x1f4da;System.out.println(“&#x1f388;如果文章中有错误的地方&#xff0c;恳请大家指正&a…