GO学习之 条件变量 sync.Cond

GO系列

1、GO学习之Hello World

2、GO学习之入门语法

3、GO学习之切片操作

4、GO学习之 Map 操作

5、GO学习之 结构体 操作

6、GO学习之 通道(Channel)

7、GO学习之 多线程(goroutine)

8、GO学习之 函数(Function)

9、GO学习之 接口(Interface)

10、GO学习之 网络通信(Net/Http)

11、GO学习之 微框架(Gin)

12、GO学习之 数据库(mysql)

13、GO学习之 数据库(Redis)

14、GO学习之 搜索引擎(ElasticSearch)

15、GO学习之 消息队列(Kafka)

16、GO学习之 远程过程调用(RPC)

17、GO学习之 goroutine的调度原理

18、GO学习之 通道(nil Channel妙用)

19、GO学习之 同步操作sync包

20、GO学习之 互斥锁、读写锁该如何取舍

21、GO学习之 条件变量 sync.Cond

文章目录

前言

按照公司目前的任务,go 学习是必经之路了,虽然行业卷,不过技多不压身,依旧努力!!!
sync.Cond 是 Go 语言中实现的传统的条件变量。那什么是条件变量呢?一个条件变量可以理解为一个容器,容器中存放着一个或者一组等待着某个条件成立的 goroutine,当条件成立是这些处于等待状态的 goroutine 将得到通知并唤醒继续执行。就类似于比赛前跑到开始处预备好的运动员,等待裁判的一声枪响,砰的一声他们就开始狂奔了。

sync.Cond 如何使用

如果没有条件变量,我们可能在 goroutine 中通过连续轮询的方式检查是否满足条件然后继续执行。轮询是非常消耗资源的,因为 goroutine 在这个过程中处于活动状态但并没有实际工作进展,我们先来看一个使用 sync.Mutex 实现的条件轮询等待的例子,如下:

go 复制代码
package main

import (
	"fmt"
	"sync"
	"time"
)

type signal struct{}

// 控制条件
var ifOk bool

func worker(i int) {
	fmt.Printf("Workder %d is working...\n", i)
	time.Sleep(1 * time.Second)
	fmt.Printf("Workder %d is finish...\n", i)
}

func spawnGroup(f func(i int), num int, mu *sync.Mutex) <-chan signal {
	c := make(chan signal)
	// 声明一个线程组
	var wg sync.WaitGroup
	// 循环启动 5 个goroutine
	for i := 0; i < num; i++ {
		wg.Add(1)
		go func(i int) {
			for {
				mu.Lock()
				if !ifOk {
					mu.Unlock()
					time.Sleep(100 * time.Millisecond)
					continue
				}
				mu.Unlock()
				fmt.Printf("worker %d: start to work... \n", i)
				f(i)
				wg.Done()
				return
			}
		}(i + 1)
	}
	// 启动一个 goroutine,等待着,直到 组里面 的 goroutine 数量为 0
	go func() {
		wg.Wait()
		c <- signal(struct{}{})
	}()
	return c
}

func main() {
	fmt.Println("Start a group of workers...")
	// 初始化一个互斥锁
	mu := &sync.Mutex{}
	// 通过 spawnGroup 函数启动 5 个 goroutine
	c := spawnGroup(worker, 5, mu)

	time.Sleep(5 * time.Second)
	fmt.Println("The group of workers start to work...")

	mu.Lock()
	ifOk = true
	mu.Unlock()
	// 从通道中接受数据,这里只从通道中取出即可
	<-c
	fmt.Println("The group of workers work done!")
}

上面的示例是使用 sync.Mutex 来实现保护临界区资源的,不过性能上不够好,因为有很多空轮询是很消耗资源的。
sync.Cond 为 goroutine 在上述场景下提供了另一种可选的、资源消耗更小、使用体验更佳的同步方式,条件变量原语,避免轮询,用 sync.Cond 对上面的例子进行改造,如下:

go 复制代码
package main

import (
	"fmt"
	"sync"
	"time"
)

type signal struct{}

// 控制条件
var ifOk bool

func worker(i int) {
	fmt.Printf("Workder %d is working...\n", i)
	time.Sleep(1 * time.Second)
	fmt.Printf("Workder %d is finish...\n", i)
}

func spawnGroup(f func(i int), num int, cond *sync.Cond) <-chan signal {
	c := make(chan signal)
	// 声明一个线程组
	var wg sync.WaitGroup
	// 循环启动 5 个goroutine
	for i := 0; i < num; i++ {
		wg.Add(1)
		go func(i int) {
			cond.L.Lock()
			for !ifOk {
				cond.Wait()
			}
			cond.L.Unlock()
			fmt.Printf("worker %d: start to work... \n", i)
			f(i)
			wg.Done()
		}(i + 1)
	}
	// 启动一个 goroutine,等待着,直到 组里面 的 goroutine 数量为 0
	go func() {
		wg.Wait()
		c <- signal(struct{}{})
	}()
	return c
}

func main() {
	fmt.Println("Start a group of workers...")
	// 初始化一个条件锁
	cond := sync.NewCond(&sync.Mutex{})
	// 通过 spawnGroup 函数启动 5 个 goroutine
	c := spawnGroup(worker, 5, cond)

	time.Sleep(5 * time.Second)
	fmt.Println("The group of workers start to work...")

	cond.L.Lock()
	ifOk = true
	// 调用 sync.Cond 的 Broadcast方法后,阻塞的 goroutine 将被唤醒并从 wait 方法中返回
	cond.Broadcast()
	cond.L.Unlock()
	// 从通道中接受数据,这里只从通道中取出即可
	<-c
	fmt.Println("The group of workers work done!")
}

运行结果:

bash 复制代码
Start a group of workers...
The group of workers start to work...
worker 5: start to work...
Workder 5 is working...
worker 2: start to work...
Workder 2 is working...
worker 3: start to work...
Workder 3 is working...
worker 4: start to work...
Workder 4 is working...
worker 1: start to work...
Workder 1 is working...
Workder 5 is finish...
Workder 2 is finish...
Workder 3 is finish...
Workder 4 is finish...
Workder 1 is finish...
The group of workers work done!

上面的实例中,sync.Cond 实例的初始化需要一个满足实现了sync.Locker接口的类型实例,通常使用 sync.Mutex。条件变量需要互斥锁来同步临界区数据。各个等待条件成立的 goroutine 在加锁后判断条件是否成立,如果不成立,则调用 sync.CondWait 方法进去等待状态。Wait 方法在 goroutine 挂起前会进行 Unlock 操作。

在 main 方法中将 ifOk 设置为 true 并调用了 sync.CondBroadcast 方法后,各个阻塞的 goroutine 将被唤醒并从 Wait 方法中返回。在 Wait 方法返回前,Wait 方法会再次加锁让 goroutine 进入临界区。接下来 goroutine 会再次对条件数据进行判定,如果人条件成立,则解锁并进入下一个工作阶段;如果条件还是不成立,那么再次调用 Wait 方法挂起等待。


现阶段还是对 Go 语言的学习阶段,想必有一些地方考虑的不全面,本文示例全部是亲自手敲代码并且执行通过。
如有问题,还请指教。

评论去告诉我哦!!!一起学习一起进步!!!

相关推荐
啃火龙果的兔子几秒前
快速搭建Java服务指南
java·开发语言
未来之窗软件服务2 分钟前
智慧收银系统开发进销存库存统计,便利店、水果店、建材与家居行业的库存汇总管理—仙盟创梦IDE
java·开发语言·ide·进销存·仙盟创梦ide·东方仙盟·收银台
pusue_the_sun11 分钟前
从零开始搞定类和对象(上)
开发语言·c++
归云鹤25 分钟前
QT信号和槽怎么传输自己定义的数据结构
开发语言·数据结构·qt
ytttr87338 分钟前
MATLAB 实现 SRCNN 图像超分辨率重建
开发语言·matlab·超分辨率重建
饭碗的彼岸one44 分钟前
重生之我在10天内卷赢C++ - DAY 1
linux·开发语言·c++·经验分享·笔记·学习方法
项目申报小狂人1 小时前
2025年1中科院1区顶刊SCI-投影迭代优化算法Projection Iterative Methods-附完整Matlab免费代码
开发语言·算法·matlab
weixin_437499921 小时前
【PHP属性详解:从基础到只读的完全指南】
开发语言·php
枫叶落雨2221 小时前
Hutool 的 WordTree(敏感词检测)
java·开发语言
Layflok2 小时前
《黑马笔记》 --- C++ 提高编程
开发语言·c++·stl