這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
概述
sync包提供了基本的同步基元,如互斥鎖。除了Once和WaitGroup類型,大部分都是適用於低水平程式線程,高水平的同步使用channel通訊更好一些。
本包的類型的值不應被拷貝。
雖然文檔解釋可能不夠深入,或者淺顯易懂,但是我覺得還是貼出來,對比瞭解可能會更好。
Go語言中實現並發或者是建立一個goroutine很簡單,只需要在函數前面加上"go",就可以了,那麼並發中,如何?多個goroutine之間的同步和通訊?答: channel 我是第一個想到的, sync, 原子操作atomic等都可以。
首先我們先來介紹一下sync包下的各種類型。那麼我們先來羅列一下sync包下所有的類型吧。
1. Cond 條件等待
type Cond struct { // L is held while observing or changing the condition L Locker // contains filtered or unexported fields}
解釋:
Cond實現了一個條件變數,一個線程集合地,供線程等待或者宣布某事件的發生。
每個Cond執行個體都有一個相關的鎖(一般是*Mutex或*RWMutex類型的值),它必須在改變條件時或者調用Wait方法時保持鎖定。Cond可以建立為其他結構體的欄位,Cond在開始使用後不能被拷貝。
條件等待通過Wait讓常式等待,通過Signal讓一個等待的常式繼續,通過Broadcase讓所有等待的繼續。
在Wait之前需要手動為c.L上鎖, Wait結束了手動解鎖。為避免虛假喚醒, 需要將Wait放到一個條件判斷的迴圈中,官方要求寫法:
c.L.Lock()for !condition() { c.Wait()}// 執行條件滿足之後的動作...c.L.Unlock()
成員文檔:
type Cond struct { L Locker // 在“檢查條件”或“更改條件”時 L 應該鎖定。} // 建立一個條件等待func NewCond(l Locker) *Cond// Broadcast 喚醒所有等待的 Wait,建議在“更改條件”時鎖定 c.L,更改完畢再解鎖。func (c *Cond) Broadcast()// Signal 喚醒一個等待的 Wait,建議在“更改條件”時鎖定 c.L,更改完畢再解鎖。func (c *Cond) Signal()// Wait 會解鎖 c.L 並進入等待狀態,在被喚醒時,會重新鎖定 c.Lfunc (c *Cond) Wait()
程式碼範例:
package mainimport ("fmt""sync""time")func main() {condition := false // 條件不滿足var mu sync.Mutexcond := sync.NewCond(&mu) // 建立一個Cond//讓協程去創造條件go func() {mu.Lock()condition = true // 改寫條件time.Sleep(3 * time.Second)cond.Signal() // 發送通知:條件okmu.Unlock()}()mu.Lock()// 檢查條件是否滿足,避免虛假通知,同時避免 Signal 提前於 Wait 執行。for !condition { // 如果Signal提前執行了,那麼此處就是false了// 等待條件滿足的通知,如果虛假通知,則繼續迴圈等待cond.Wait() // 等待時 mu 處於解鎖狀態,喚醒時重新鎖定。 (阻塞當前線程)}fmt.Println("條件滿足,開始後續動作...")mu.Unlock()}
2. Locker
type Locker interface { Lock() Unlock()}
Locker介面代表一個可以加鎖和解鎖的對象。 是一個介面。
3. Mutex 互斥鎖
type Mutex struct { // contains filtered or unexported fields}
解釋:
Mutex 是互斥鎖。Mutex 的零值是一個解鎖的互斥鎖。 第一次使用後不得複製 Mutex 。
互斥鎖是用來保證在任一時刻, 只能有一個常式訪問某個對象。 Mutex的初始值為解鎖的狀態。 通常作為其他結構體的你名欄位使用, 並且可以安全的在多個常式中並行使用。
成員文檔:
// Lock 用於鎖住 m,如果 m 已經被加鎖,則 Lock 將被阻塞,直到 m 被解鎖。func (m *Mutex) Lock()// Unlock 用於解鎖 m,如果 m 未加鎖,則該操作會引發 panic。func (m *Mutex) Unlock()
程式碼範例:
package mainimport ("fmt""sync")type SafeInt struct {sync.MutexNum int}func main() {waitNum := 10 // 設定等待的個數(繼續往下看)count := SafeInt{}done := make(chan bool)for i := 0; i < waitNum; i++ {go func(i int) {count.Lock() // 加鎖,防止其它常式修改 countcount.Num = count.Num + ifmt.Print(count.Num, " ")count.Unlock()done <- true}(i)}for i := 0; i < waitNum; i++ {<-done}}
[ `go run sync_mutex.go` | done: 216.47974ms ]
1 4 8 8 10 15 21 30 37 45
注意:多次輸出結果不一致, 試想為什麼會出現10個結果中有0值得, 為什麼10個結果中都大於0呢?或者都大於1呢? 那麼會不會出現10個結果中最小值是9 呢?
4. Once 單次執行
type Once struct { // contains filtered or unexported fields}
解釋:
Once是只執行一次動作的對象。
Once 的作用是多次調用但只執行一次,Once 只有一個方法,Once.Do(),向 Do 傳入一個函數,這個函數在第一次執行 Once.Do() 的時候會被調用,以後再執行 Once.Do() 將沒有任何動作,即使傳入了其它的函數,也不會被執行,如果要執行其它函數,需要重新建立一個 Once 對象。
成員文檔:
// 多次調用僅執行一次指定的函數 ffunc (o *Once) Do(f func())
程式碼範例:
package main// 官方案例import ("fmt""sync")func main() {var once sync.Oncevar num intonceBody := func() {fmt.Println("Only once")}done := make(chan bool)for i := 0; i < 10; i++ {go func() {once.Do(onceBody) // 多次調用done <- true}()}for i := 0; i < 10; i++ {<-done}}
待續...