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 -
推薦閱讀
關注加星標,瞭解「編程技術之道」