Go中sync 包Cond使用场景分析
作者:水淹萌龙 发布时间:2024-01-31 20:50:05
背景
编写代码过程中, 通常有主协程和多个子协程进行协作的过程,比如通过 WaitGroup 可以实现当所有子协程完成之后, 主协程再继续执行, 具体可参考:Go 中goroutine和WaitGroup的使用
如上的场景是主协程等待子协程达到某个状态再继续运行。 但是反过来怎么操作呢,要求一组子协程等待主协达到某个状态时才继续运行。这个时候就需要用到 Cond 了
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个号就结束了
代码如下:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func main() {
c := sync.NewCond(&sync.Mutex{})
num := 0
// 当前叫号是几号
hand_num := 0
for i := 0; i < 5; i++ {
go func(i int) {
// 分别在不同时间排队
time.Sleep(time.Second * time.Duration(rand.Int63n(10)))
c.L.Lock()
num++
// 当前取得号。
cur := num
fmt.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 = cur
c.L.Unlock()
}(i)
}
// 都叫号了
for hand_num != 5 {
// 叫号
c.Signal()
time.Sleep(time.Second * 1)
}
time.Sleep(time.Second * 10)
}
执行结果如下
结果表明,5个病人,分别在三秒钟内先后取号, 然后护士每过一秒钟按照排队的先后顺序叫一个号(叫号的过程依然有病人取号),先取号的被先叫号。
此场景中,5个病人相当于5个协程, 主协程反复使用Signal()
按照顺序一个个唤醒阻塞的子协程。
Broadcast的使用场景
场景为如下: 运动员跑步比赛,要求8秒内全部运动员准备好,然后等待教练发令, 教练10秒后发令,所有运动员在发令后开始跑。
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func main() {
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)
}
执行结果如下:
如结果所示, 10个运动员在8秒内分别准备好,等待教练发令后,同时开跑。
此场景中,10个运动员相当于10个协程, 同时等待主协程的命令,使用Broadcast()
唤醒所有阻塞的子协程。
注意事项
使用 Cond,最容易踩的坑就是调用 Wait()
方法之前,调用者没有持有锁或没有检查辅助条件。
在如上示例代码中,假如把调用 Wait()
方法前后的加锁和释放锁的代码注释掉,运行代码会导致程序 panic。原因是调用 Wait 方法,会先把调用者放入等待队列中,然后释放锁。此时如果在未持有锁时调用释放锁的方法,就会导致程序 panic。
来源:https://blog.csdn.net/qq_21047625/article/details/129280580
猜你喜欢
- pycurl是一个用c语言编写的libcurl Python实现,功能非常强大,支持操作协议有FTP,HTTP,HTTPS,TELNET等。
- 一:创建迁移在laravel中使用make:migration命令来创建迁移php artisan make:migration creat
- IE 浏览器中 CSS Expression 特性的最大的问题:会反复执行,每秒钟可能执行了成百上千次,有严重的性能问题。如何对 CSS E
- 原因:list 获得的数据为空: 显示值为 [ ]不同的判断--- is None----not两者结果不一样分析:总之:not 判断的是内
- 我们有时候会需要在网上查找并下载图片,当数量比较少的时候,点击右键保存,很轻松就可以实现图片的下载,但是有些图片进行了特殊设置,点击右键没有
- 当代码已经写得差不多,发现某个变量名需要修改,但代码中很多地方都有该变量,一一修改太麻烦了,在不同的情景下,可以采取更加简便的方法,如下介绍
- 设置Table的细边框通常有这么几种方式:1、设置边框的BORDER=0 、cellspacing=1,设置Table的背景色为所要的边框色
- 日志中也没有打印什么明显的错误,只是显示连接了rabbitmq后就关闭了[2019-09-11 06:08:45,729: INFO/Bea
- 1、有一个论坛,帖子的数据巨大,请简要说明如何提高用户搜索帖子的效率。 在程序方面,可以使用页面缓存技术。在前台界面着设计方面也可以让用户输
- php实现记住密码自动登录方法不止一个,下面出现有二个emptyempty,其实是一个,那是因为代码高亮有bug。希望对大家有帮助。 一,用
- 导语提到《俄罗斯方块》(Tetris),那真是几乎无人不知无人不晓。其历史之悠久,可玩性之持久,能手轻轻一挥,吊打一 * 游戏。对于绝大多数小
- 方法一、os.system() 会保存可执行程序中的打印值和主函数的返回值,且会将执行过程中要打印的内容打印出来import os main
- MySQL主从设置MySQL主从复制,读写分离的设置非常简单:修改配置my.cnf文件master 和 slave设置的差不多:[mysql
- 误区10.数据库镜像在故障发生后,马上就能发现 错误 市面上大肆宣传数据库镜像技术可以在故障发生后,立即检测到错误并进行故障转移。 但事实并
- 使用Ajax技术网页应用能够快速地将增量更新呈现在用户界面上,而不需要重载刷新整个页面,这使得程序能够更快地回应用户的操作,如下笔记将简单介
- 读取nc数据相关信息#导入库import netCDF4from netCDF4 import Dataset#读取数据文件nc
- 一、base64模块base64模块提供了在二进制数据和可打印ASCII字符间编解码的功能,包括 RFC3548中定义的Base16, Ba
- 本文实例讲述了Python基于正则表达式实现文件内容替换的方法。分享给大家供大家参考,具体如下:最近因为有一个项目需要从普通的服务器移植到S
- 句柄(handle)是C++程序设计中经常提及的一个术语。它并不是一种具体的、固定不变的数据类型或实体,而是代表了程序设计中的一个广义的概念
- 在 CentOS7 中我们在安装 MySQL 的话会默认安装的是 MariaDB。它是一个在 MySQL 被收购之后,作者推出的应一个开源版