天天看點

golang 系列:sync.Cond 機制

前言

在 Go 裡有專門為同步通信而生的

channel

,是以較少看到 sync.Cond 的使用。不過它也是并發控制手段裡的一種,今天我們就來認識下它的相關實作,加深對同步機制的運用。

sync.Cond

sync.Cond 提供了三個方法:Wait()、Signal()、Broadcast(),它們的用法如下:

  • Wait():阻塞目前的 goroutine,等待喚起。
  • Signal():喚起一個阻塞的 goroutine。
  • Broadcast():喚起所有阻塞的 goroutine。

通過上面的方法描述,我們就可以簡單的實作一個任務池功能:先批量的建立 goroutine,然後調用 sync.Cond 的 Wait() 方法讓其阻塞的等待。

當有一個任務到來時,則通過 Signal() 喚起剛剛在阻塞的某一個 goroutine,去執行任務。

通過任務池功能,我們發現 sync.Cond 的運用很簡單,但 Go 官方并不推薦我們使用 sync.Cond 來實作協程間的同步通信。

因為它并不符合 Go 官方 “通過通信來共享記憶體” 的設計思想,當場景複雜時,則會耦合各個業務功能。

sync.Cond 源碼分析

我們來看下 sync.Cond 的結構體,代碼在 /sr/sync/cond.go 下:

type Cond struct {
    noCopy noCopy         // 不可複制
    L Locker            // 鎖
    notify  notifyList    // 通知喚起清單
    checker copyChecker // 複制檢測
}           

可以看到 Cond 上有 notify 清單,而這正是維護了需要喚起的 goroutine 清單。

當我們調用 Wait() 方法的時候就會維護目前 goroutine 到對應的 notifyList 裡:

func (c *Cond) Wait() {
    c.checker.check()
    t := runtime_notifyListAdd(&c.notify) // 将目前 goroutine 添加到 notifyList 裡
    c.L.Unlock()
    runtime_notifyListWait(&c.notify, t) // 阻塞等待
    c.L.Lock()
}           

當有其他協程調用了 Signal 或 Broadcast 方法時,則會通過

runtime_notifyListNotifyOne

runtime_notifyListNotifyAll

方法來喚起一個或多個 goroutine。

其他同步方式的實作

前面提到到 sync.Cond 并不被推薦作為協同通信手段,那如果要實作它的單點傳播、廣播效果,該怎麼弄呢?

其實也很簡單,如果我們要實作單點傳播效果,那麼隻需要通過阻塞的監聽 channel 信号即可。

如果要實作廣播喚起效果,隻需要利用

context

的鍊式取消特性,也能達到該效果。