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

sync.Cond 如何使用

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

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 对上面的例子进行改造,如下:

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!")
}

运行结果:

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

你可能感兴趣的:(golang学习,golang,开发语言)