Golang语言标准库 sync 包的 Cond 怎么使用?

简介: Golang语言标准库 sync 包的 Cond 怎么使用?

image.jpeg

01

介绍


在 Go 语言中,Cond 实现一个条件变量,协助解决等待或通知事件场景的并发执行问题,通常用于等待某个条件的一组 goroutine。这个条件需要一组 goroutine 共同协作完成,如果条件为 false,所有等待这个条件的 goroutine 将会被阻塞,当这个条件变为 true 时,所有等待这个条件的其中一个 goroutine 或者所有 goroutine 会被唤醒。


02

基本用法


通过阅读源码,我们可以发现 Cond 关联一个 Locker L(通常是 *Mutex 或 *RWMutex),在更新条件和调用 Wait 方法时必须持有该锁 L。并且,首次使用后不得复制 Cond。通常,使用 NewCond 函数创建一个 Cond。


// A Cond must not be copied after first use.
type Cond struct {
  noCopy noCopy
  // L is held while observing or changing the condition
  L Locker
  notify  notifyList
  checker copyChecker
}
// NewCond returns a new Cond with Locker l.
func NewCond(l Locker) *Cond {
  return &Cond{L: l}
}
func (c *Cond) Wait() {
  c.checker.check()
  t := runtime_notifyListAdd(&c.notify)
  c.L.Unlock()
  runtime_notifyListWait(&c.notify, t)
  c.L.Lock()
}
func (c *Cond) Signal() {
  c.checker.check()
  runtime_notifyListNotifyOne(&c.notify)
}
func (c *Cond) Broadcast() {
  c.checker.check()
  runtime_notifyListNotifyAll(&c.notify)
}


Cond 只有 3 个方法,分别是 Wait、Signal 和 Broadcast。下面分别介绍一下这 3 个方法。

  • Wait:把调用者放入等待队列中并阻塞,直到被 Signal 方法或 Broadcast 方法在等待队列中移除并唤醒。调用 Wait 方法之前,调用者必须持有锁。
  • Signal:调用者将等待队列中的 goroutine 移除第一个,并唤醒被移除的 goroutine。
  • Broadcast:调用者将等待队列中的所有 goroutine 全部移除,并全部唤醒。


了解了 Cond 的 3 个方法,我们通过实现一个「学生报名参加课外活动」的简单示例,演示如何使用 Cond。


640.png


其中,需要注意的是 Wait 方法。调用者在调用 Wait 方法之前,必须持有锁,并且每次调用都要检查辅助条件变量 count。


03

实现原理


通过阅读 Part 02 的源码,可以发现 Cond 实现非常简单,主要是通过 Wait 方法将调用者放入一个等待队列中并阻塞,其他 goroutine 去检查或更新条件变量,然后通过调用 Signal 方法或 Broadcast 方法唤醒等待队列中的一个或全部 goroutine。


04

踩坑


使用 Cond,最容易踩的坑就是调用 Wait 方法之前,调用者没有持有锁或没有检查辅助条件。


在 Part 02 的示例代码中,假如把调用 Wait 方法前后的加锁和释放锁的代码注释掉,运行代码会导致程序 panic。原因是调用 Wait 方法,会先把调用者放入等待队列中,然后释放锁。此时如果在未持有锁时调用释放锁的方法,就会导致程序 panic。


还有就是等待队列中的 goroutine 被唤醒,并不代表条件满足,还要再次检查辅助条件是否满足。


05

总结


本文开篇介绍了 Cond 的用途,然后结合源码介绍了 Cond 的实现和 3 个方法,并通过一个「学生报名参加课外活动」的模拟示例演示了 Cond 的基本使用,最后列举了一个非常容易踩的「坑」。





目录
相关文章
|
2月前
|
Go
Golang语言之gRPC程序设计示例
这篇文章是关于Golang语言使用gRPC进行程序设计的详细教程,涵盖了RPC协议的介绍、gRPC环境的搭建、Protocol Buffers的使用、gRPC服务的编写和通信示例。
104 3
Golang语言之gRPC程序设计示例
|
2月前
|
Prometheus Cloud Native Go
Golang语言之Prometheus的日志模块使用案例
这篇文章是关于如何在Golang语言项目中使用Prometheus的日志模块的案例,包括源代码编写、编译和测试步骤。
55 3
Golang语言之Prometheus的日志模块使用案例
|
1月前
|
前端开发 中间件 Go
实践Golang语言N层应用架构
【10月更文挑战第2天】本文介绍了如何在Go语言中使用Gin框架实现N层体系结构,借鉴了J2EE平台的多层分布式应用程序模型。文章首先概述了N层体系结构的基本概念,接着详细列出了Go语言中对应的构件名称,包括前端框架(如Vue.js、React)、Gin的处理函数和中间件、依赖注入和配置管理、会话管理和ORM库(如gorm或ent)。最后,提供了具体的代码示例,展示了如何实现HTTP请求处理、会话管理和数据库操作。
31 0
|
2月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
115 4
Golang语言之管道channel快速入门篇
|
2月前
|
Go
Golang语言文件操作快速入门篇
这篇文章是关于Go语言文件操作快速入门的教程,涵盖了文件的读取、写入、复制操作以及使用标准库中的ioutil、bufio、os等包进行文件操作的详细案例。
67 4
Golang语言文件操作快速入门篇
|
2月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
86 4
|
2月前
|
Go
Golang语言错误处理机制
这篇文章是关于Golang语言错误处理机制的教程,介绍了使用defer结合recover捕获错误、基于errors.New自定义错误以及使用panic抛出自定义错误的方法。
48 3
|
2月前
|
Go 调度
Golang语言goroutine协程篇
这篇文章是关于Go语言goroutine协程的详细教程,涵盖了并发编程的常见术语、goroutine的创建和调度、使用sync.WaitGroup控制协程退出以及如何通过GOMAXPROCS设置程序并发时占用的CPU逻辑核心数。
52 4
Golang语言goroutine协程篇
|
2月前
|
Go
Golang语言之函数(func)进阶篇
这篇文章是关于Golang语言中函数高级用法的教程,涵盖了初始化函数、匿名函数、闭包函数、高阶函数、defer关键字以及系统函数的使用和案例。
64 3
Golang语言之函数(func)进阶篇
|
2月前
|
Go
Golang语言之函数(func)基础篇
这篇文章深入讲解了Golang语言中函数的定义和使用,包括函数的引入原因、使用细节、定义语法,并通过多个案例展示了如何定义不返回任何参数、返回一个或多个参数、返回值命名、可变参数的函数,同时探讨了函数默认值传递、指针传递、函数作为变量和参数、自定义数据类型以及返回值为切片类型的函数。
63 2
Golang语言之函数(func)基础篇