云原生探索系列(十七):Go 语言sync.Cond

前言

在并发编程中,条件变量是一种常见的同步机制,它允许 goroutine 在某个条件满足时继续执行。比如你要做一顿丰盛的晚餐, 大概分为3个步骤,首先,熬汤,然后,做主菜,最后,上菜,主菜不能提前做,因为它容易冷掉,必须等汤煮好之后才做。 在这个过程中,做主菜的任务依赖于汤煮好的条件。若你在汤还没煮好的时候开始做主菜,主菜可能会做得过早而变凉。这就需要你在做主菜之前等待汤煮好。 那该如何知道汤已经煮好了,可以做主菜了?

Go 语言提供了 sync.Cond 类型来实现这一功能, sync.Cond 是一个用于协调多个 goroutine 之间的通信和同步的工具。 sync.Cond 就像一个信号器,在"汤煮好"的时候,通知等待的 goroutine (比如做主菜的那个人)继续执行。

什么是 sync.Cond?

sync.Cond 是 Go 标准库 sync 包中的一个结构体,它提供了一个条件变量的实现,允许 goroutine 在等待某个条件时挂起,直到条件满足时被唤醒。

sync.Cond 实际上是与互斥锁(sync.Mutex )或读写锁(sync.RWMutex )结合使用的,通常它通过锁来保护共享状态,并使用条件变量来协调 goroutine 之间的执行顺序。

基础用法

  • Wait :使当前 goroutine 等待条件满足。在调用 Wait 之前,必须持有 sync.Cond 关联的锁。调用 Wait 会释放该锁,并使当前 goroutine 进入阻塞状态,直到其他 goroutine 调用 SignalBroadcast 来唤醒它。
  • Signal :唤醒一个等待该条件的 goroutine 。如果有多个 goroutine 在等待, Signal 只会唤醒一个。
  • Broadcast :唤醒所有等待该条件的 goroutine
  • NewCond :创建一个新的 sync.Cond ,并指定关联的互斥锁。

工作原理

sync.Cond 通常与互斥锁结合使用,锁用于保护共享资源,条件变量用于在特定条件下协调 goroutine 。具体流程如下:

  1. 一个 goroutine 在某个条件不满足时调用 Wait ,它会释放锁并进入等待状态。
  2. 当另一个 goroutine 改变了共享资源的状态并使条件满足时,它会调用 SignalBroadcast 来唤醒一个或多个等待中的 goroutine
  3. 被唤醒的 goroutine 会重新尝试获取锁,并在锁可用时继续执行。

使用示例

我们来实现,文章开头做晚餐的案例:

erlang 复制代码
package main

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

type Meal struct {
	cond *sync.Cond
}

func NewMeal() *Meal {
	m := &Meal{}
	var lock sync.Mutex
	m.cond = sync.NewCond(&lock) // 通过互斥锁创建条件变量
	return m
}

func (m *Meal) CookSoup() {
	fmt.Println("开始煮汤...")
	time.Sleep(3 * time.Second)
	fmt.Println("汤煮好了!")
	m.cond.Signal()
}

func (m *Meal) CookMainDish() {
	fmt.Println("做主菜,请等待汤煮好...")
	m.cond.L.Lock()
	m.cond.Wait()
	m.cond.L.Unlock()

	fmt.Println("汤煮好了,可以开始做主菜了!")
}

func main() {
	meal := NewMeal()

	go meal.CookMainDish()

	go meal.CookSoup()

	time.Sleep(5 * time.Second)
}

这段代码中:

  • Meal 结构体:包含了一个条件变量 cond ,用于协调"煮汤"和"做主菜"之间的关系。
  • CookSoup 方法:模拟煮汤的过程,煮好汤后,调用 Signal() 唤醒等待的主菜 goroutine
  • CookMainDish 方法:首先等待汤煮好(通过 cond.Wait() ),直到条件满足才能继续做主菜。 执行后,输出内容如下:
erlang 复制代码
做主菜,请等待汤煮好...
开始煮汤...
汤煮好了!
汤煮好了,可以开始做主菜了!

这个场景的核心:

  • 条件等待与通知: sync.Cond 允许 goroutine 在某个条件未满足时进入阻塞等待,直到其他 goroutine 满足条件并通过 Signal()Broadcast() 唤醒等待的 goroutine
  • 协调多个任务:就像在做晚餐时,"做主菜"的人不能在"汤煮好"之前开始工作, sync.Cond 在这里就充当了协调者,确保任务的顺序。

Wait 方法内部机制

  1. 把调用它的(当前的) goroutine 加入到当前条件变量的通知队列中
  2. 解锁当前的条件变量基于的互斥锁
  3. 让当前的 goroutine 处于等待状态,等到通知到来时再决定是否唤醒它。此时,这个 goroutine 就会阻塞在调用 Wait 方法的那行代码上
  4. 通知到来且决定唤醒这个 goroutine ,那就在唤醒之后重新锁定当前条件变量基于的互斥锁。当前的 goroutine 就会继续执行后面的代码了。

SignalBroadcast 区别

  • Signal 唤醒一个为此等待的 goroutineBroadcast 唤醒所有为此等待的 goroutine
  • Wait 方法会把当前的 goroutine 加入到通知队列的队尾, Signal 总会从通知队列的队首开始查找可被唤醒的 goroutine 。换句话说,一般都是最早等待的那一个
  • SignalBroadcastWait 方法不同,不需要在互斥锁的保护下进行,最好在解锁条件变量基于的那个互斥锁后调用。

总结

sync.Cond 是一个强大的同步工具,适用于需要协调多个 goroutine 之间依赖条件的场景。它可以与互斥锁结合使用,提供一种灵活的机制来在某些条件满足时唤醒等待的 goroutine。 在实际开发中,合理使用条件变量能够有效地解决并发冲突,提高程序的性能和稳定性。

相关推荐
repetitiononeoneday34 分钟前
云原生课程-Docker
云原生
KubeSphere 云原生35 分钟前
云原生周刊:Kubernetes v1.33 正式发布
云原生·容器·kubernetes
南 阳42 分钟前
从微服务到AI服务:Nacos 3.0如何重构下一代动态治理体系?
人工智能·微服务·云原生·重构
codingandsleeping44 分钟前
Express入门
javascript·后端·node.js
ss2731 小时前
基于Springboot + vue + 爬虫实现的高考志愿智能推荐系统
spring boot·后端·高考
专注API从业者2 小时前
《Go 语言高并发爬虫开发:淘宝商品 API 实时采集与 ETL 数据处理管道》
开发语言·后端·爬虫·golang
Asthenia04122 小时前
Netty writeAndFlush与Pipeline深入分析
后端
欧先生^_^3 小时前
Scala语法基础
开发语言·后端·scala
GetcharZp3 小时前
xterm.js 终端神器到底有多强?用了才知道!
前端·后端·go
洞窝技术4 小时前
MYSQL:关于索引你想知道的
后端·mysql