云原生探索系列(十七):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。 在实际开发中,合理使用条件变量能够有效地解决并发冲突,提高程序的性能和稳定性。

相关推荐
duapple2 小时前
Golang基于反射的ioctl实现
开发语言·后端·golang
牛奔4 小时前
Docker配置远程连接
运维·docker·云原生·容器·eureka
my_styles5 小时前
docker-compose部署项目(springboot服务)以及基础环境(mysql、redis等)ruoyi-ry
spring boot·redis·后端·mysql·spring cloud·docker·容器
免檒7 小时前
go语言协程调度器 GPM 模型
开发语言·后端·golang
不知道写什么的作者7 小时前
Flask快速入门和问答项目源码
后端·python·flask
天天爱吃肉82187 小时前
【低成本STM32的T-BOX开发实战:高可靠的车联网解决方案】
stm32·单片机·嵌入式硬件·云原生
caihuayuan58 小时前
生产模式下react项目报错minified react error #130的问题
java·大数据·spring boot·后端·课程设计
一只码代码的章鱼8 小时前
Spring Boot- 2 (数万字入门教程 ):数据交互篇
spring boot·后端·交互
庸子8 小时前
Serverless技术深度整合:从冷启动优化到边缘场景落地
云原生·架构·serverless
不再幻想,脚踏实地11 小时前
Spring AOP从0到1
java·后端·spring