GO语言中有句名言:“不要用共享内存来通讯,而是使用通讯来共享内存”。算法
编程语言中,通讯方式分为进程间通讯、线程间通讯。编程
对于Go语言来讲,Go程序启动以后对外是一个进程,内部包含若干协程,协程至关于用户态轻量级线程,因此协程的通讯方式大多可使用线程间通讯方式来完成。架构
协程间通讯方式,官方推荐使用channel,channel在一对一的协程之间进行数据交换与通讯十分便捷。可是,一对多的广播场景中,则显得有点无力,此时就须要sync.Cond来辅助。并发
举个例子,上高中时,宿管老师天天早晨须要叫醒学生们去上课。有两种方法:①一个寝室一个寝室把学生叫醒 ②在宿舍楼安装广播,到起床时间,在广播上叫醒学生。显然,使用广播的方式效率更高。
编程中的广播能够理解为:多个操做流程依赖于一个操做流程完成后才能进行某种动做,这个被依赖的操做流程在唤醒全部依赖者时使用的一种通知方式。less
在Go语言中,则可使用sync.Cond来实现多个协程之间的广播通知功能。socket
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() // 阻塞等待,直到被唤醒`
对应源码追溯oop
`// 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)` `}`
使用方法,代码示例:ui
`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 < 10; i++ {` `gofunc(x int) {` `cond.L.Lock() // 获取锁` `defer cond.L.Unlock() // 释放锁` `cond.Wait() // 等待通知,阻塞当前 goroutine` `// 通知到来的时候, cond.Wait()就会结束阻塞, do something. 这里仅打印` `fmt.Println(x)` `}(i)` `}` `time.Sleep(time.Second * 1) // 睡眠 1 秒,等待全部 goroutine 进入 Wait 阻塞状态` `fmt.Println("Signal...")` `cond.Signal() // 1 秒后下发一个通知给已经获取锁的 goroutine` `time.Sleep(time.Second * 1)` `fmt.Println("Signal...")` `cond.Signal() // 1 秒后下发下一个通知给已经获取锁的 goroutine` `time.Sleep(time.Second * 1)` `cond.Broadcast() // 1 秒后下发广播给全部等待的goroutine` `fmt.Println("Broadcast...")` `time.Sleep(time.Second * 1) // 等待全部 goroutine 执行完毕` `}`
在go中协程间通讯的方式有多种,最经常使用的是channel。若是牵扯多个协程的通知,可使用sync.Cond。
查看channel、sync.Cond源码会发现,它们有类似之处:
虽然说有类似之处,但却有本质区别:
- THE END -
推荐阅读
关注加星标,了解「编程技术之道」