Golang 并发 Cond条件变量

Golang 并发 Cond条件变量

背景

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

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

简介

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

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

Wait

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

Signal

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

Broadcast

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

注意:调用 Signal 和 Broadcast 方法,不强求持有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.tpcf.cn/news/674549.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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…