GO系列
文章目录
前言
按照公司目前的任务,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.Cond
的 Wait
方法进去等待状态。Wait 方法在 goroutine 挂起前会进行 Unlock
操作。
在 main 方法中将 ifOk 设置为 true 并调用了 sync.Cond
的 Broadcast
方法后,各个阻塞的 goroutine 将被唤醒并从 Wait 方法中返回。在 Wait 方法返回前,Wait 方法会再次加锁让 goroutine 进入临界区。接下来 goroutine 会再次对条件数据进行判定,如果人条件成立,则解锁并进入下一个工作阶段;如果条件还是不成立,那么再次调用 Wait 方法挂起等待。
现阶段还是对 Go 语言的学习阶段,想必有一些地方考虑的不全面,本文示例全部是亲自手敲代码并且执行通过。
如有问题,还请指教。
评论去告诉我哦!!!一起学习一起进步!!!