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

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

相关推荐
isyangli_blog4 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008115 小时前
FastAPI APIRouter
开发语言·python
Benszen5 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆5 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木5 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
杨充5 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法
噜噜噜阿鲁~5 小时前
python学习笔记 | 11.3、面向对象高级编程-多重继承
java·开发语言
basketball6165 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
春生野草6 小时前
反射、Tomcat执行
java·开发语言
雪的季节7 小时前
企业级 Qt 全功能项目
开发语言·数据库·qt