共计 2755 个字符,预计需要花费 7 分钟才能阅读完成。
导读 | 在 Go 中协程间通信的方式有多种,最常用的是 channel。如果牵扯多个协程的通知,可以使用 sync.Cond。 |
1. 程序中的通信方式
GO 语言中有句名言:“不要用共享内存来通信,而是使用通信来共享内存”。
编程语言中,通信方式分为进程间通信、线程间通信。
1. 进程间通信,常用方式:
- 有名管道
- 无名管道
- 信号
- 共享内存
- 消息队列
- 信号灯集
- socket
2. 线程间通信,常用方式:
- 信号量
- 互斥锁
- 条件变量
对于 Go 语言来说,Go 程序启动之后对外是一个进程,内部包含若干协程,协程相当于用户态轻量级线程,所以协程的通信方式大多可以使用线程间通信方式来完成。
协程间通信方式,官方推荐使用 channel,channel 在一对一的协程之间进行数据交换与通信十分便捷。但是,一对多的广播场景中,则显得有点无力,此时就需要 sync.Cond 来辅助。
2. 什么是广播?
举个例子,上高中时,宿管老师每天早晨需要叫醒学生们去上课。这个时候,有两种解决方法:①一个寝室一个寝室的把学生叫醒。②在宿舍楼安装个广播,到起床时间时,在广播上叫醒学生。显然,使用广播的方式效率更高。
编程中的广播可以理解为:多个操作流程依赖于一个操作流程完成后才能进行某种动作,这个被依赖的操作流程在唤醒所有依赖者时使用的一种通知方式。
在 Go 语言中,则可以使用 sync.Cond 来实现多个协程之间的广播通知功能。
3. sync.Cond
cond 是 sync 包下面的一种数据类型,相当于线程间通信的条件变量方式。
// Cond implements a condition variable, a rendezvous point
// for goroutines waiting for or announcing the occurrence
// of an event.
//
// Each Cond has an associated Locker L (often a *Mutex or *RWMutex),
// which must be held when changing the condition and
// when calling the Wait method.
//
// A Cond must not be copied after first use.
type Cond struct {
noCopy noCopy // 在第一次使用后不可复制, 使用 go vet 作为检测使用
// L is held while observing or changing the condition
// 根据需求初始化不同的锁,如 *Mutex 和 *RWMutex。注意是 指针类型
L Locker
// 具有头尾指针的链表。存储被阻塞的协程,通知时操作该链表中的协程
notify notifyList
checker copyChecker // 复制检查, 检查 cond 实例是否被复制
}
该数据类型提供的方法有:
type Cond
func NewCond(l Locker) *Cond
func (c *Cond) Broadcast() // 通知所有协程,广播
func (c *Cond) Signal() // 通知一个协程
func (c *Cond) Wait() // 阻塞等待,直到被唤醒
对应源码追溯
// Wait atomically unlocks c.L and suspends execution
// of the calling goroutine. After later resuming execution,
// Wait locks c.L before returning. Unlike in other systems,
// Wait cannot return unless awoken by Broadcast or Signal.
//
// Because c.L is not locked when Wait first resumes, the caller
// typically cannot assume that the condition is true when
// Wait returns. Instead, the caller should Wait in a loop:
//
// 注意下面的写法是官方推荐的
// c.L.Lock()
// for !condition() {// c.Wait()
// }
// ... make use of condition ...
// c.L.Unlock()
//
func (c *Cond) Wait() {
// 检查 c 是否是被复制的,如果是就 panic
c.checker.check()
// 获取等待队列的一个 ticket 数值,作为唤醒时的一个令牌凭证
t := runtime_notifyListAdd(&c.notify)
// 解锁
c.L.Unlock()
// 注意,上面的 ticket 数值会作为阻塞携程的一个标识
// 加入通知队列里面
// 到这里执行 gopark(),当前协程挂起,直到 signal 或 broadcast 发起通知
runtime_notifyListWait(&c.notify, t)
// 被唤醒之后,先获取锁
c.L.Lock()}
// Signal wakes one goroutine waiting on c, if there is any.
//
// It is allowed but not required for the caller to hold c.L
// during the call.
func (c *Cond) Signal() {c.checker.check()
runtime_notifyListNotifyOne(&c.notify) // 随机挑选一个进行通知,wait 阻塞解除
}
// Broadcast wakes all goroutines waiting on c.
//
// It is allowed but not required for the caller to hold c.L
// during the call.
func (c *Cond) Broadcast() {c.checker.check()
// 通知所有阻塞等待的协程
// 主要是唤醒 cond.notify 链表上的各个协程
runtime_notifyListNotifyAll(&c.notify)
}
使用方法,代码示例:
var locker sync.Mutex
var cond = sync.NewCond(&locker)
// NewCond(l Locker) 里面定义的是一个接口, 拥有 lock 和 unlock 方法。// 看到 sync.Mutex 的方法,func (m *Mutex) Lock(), 可以看到是指针有这两个方法, 所以应该传递的是指针
func main() {
// 启动多个协程
for i := 0; i
总结
在 Go 中协程间通信的方式有多种,最常用的是 channel。如果牵扯多个协程的通知,可以使用 sync.Cond。
正文完
星哥玩云-微信公众号