1. 程序中的通信形式

GO语言中有句名言:“不要用共享内存来通信,而是应用通信来共享内存”。

编程语言中,通信形式分为过程间通信、线程间通信。

  1. 过程间通信,罕用形式:
  • 有名管道
  • 无名管道
  • 信号
  • 共享内存
  • 音讯队列
  • 信号灯集
  • socket
  1. 线程间通信,罕用形式:
  • 信号量
  • 互斥锁
  • 条件变量

对于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 < 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源码会发现,它们有相似之处

  1. 阻塞协程对立被封装在 sudog 构造外面
  2. channel 阻塞读/写时,用双向链表存储被阻塞导致期待唤醒的协程
  3. sync.Cond 应用带有头尾指针的单向链表存储被阻塞导致期待唤醒的协程
  4. 阻塞时都是应用gopark()进行协程的挂起操作

虽说有相似之处,但却有本质区别

  1. channel 可用来在协程间传递数据
  2. sync.Cond 不可用来在协程间传递数据,次要用来进行协程的阻塞唤醒操作。如须要传递数据,则须要应用全局变量进行传递

- THE END -

举荐浏览  

带你意识分布式系统的CAP实践

关注加星标,理解「编程技术之道」