1. 程式人生 > >Go語言---sync 包

Go語言---sync 包

sync包提供基本的同步原語,如互斥鎖。 除了Once和WaitGroup型別之外,大多數型別都是供低階庫例程使用的。經過通道和通訊可以更好地完成更高級別的同步。

互斥鎖

互斥鎖用來保證在任一時刻,只能有一個執行緒函式訪問某物件,保證共享資料的安全性防止競態。Mutex 的初始值為解鎖狀態。Mutex 通常作為其它結構體的匿名欄位使用,使該結構體具有 Lock 和 Unlock 方法。  Mutex 可以安全的在多個例程中並行使用。

// Locker 介面包裝了基本的 Lock 和 UnLock 方法,用於加鎖和解鎖。
type Locker interface {
    Lock()
    Unlock()
}
type Mutex struct {
        // contains filtered or unexported fields  //欄位沒有匯出
}
// Lock 用於鎖住 m,如果 m 已經被加鎖,則 Lock 將被阻塞,直到 m 被解鎖。
func (m *Mutex) Lock()

// Unlock 用於解鎖 m,如果 m 未加鎖,則該操作會引發 panic。
func (m *Mutex) Unlock()

程式示例:

package main

import (
	"sync"
	"fmt"
)

// 示例:互斥鎖
type SafeInt struct {
	sync.Mutex
	Num int
}

func main() {
	count := SafeInt{}

	done := make(chan bool)
	for i := 0; i < 10; i++ {
		go func(i int) {
			count.Lock() // 加鎖,防止其它例程修改 count
			//保護共享變數 count,+=操作不是原子操作   順序不定,不知道哪個協程先執行,但每次只有一個協程訪問變數
			count.Num += i
			fmt.Print(count.Num, " ")
			count.Unlock() // 修改完畢,解鎖
			done <- true
		}(i)
	}
	for i := 0; i < 10; i++ {
		<-done
	}

	fmt.Println(count.Num)   // 45
}

讀寫互斥鎖

RWMutex 比 Mutex 多了一個“讀鎖定”和“讀解鎖”,可以讓多個例程同時讀取某物件。RWMutex 的初始值為解鎖狀態。RWMutex 通常作為其它結構體的匿名欄位使用。Mutex 可以安全的在多個例程中並行使用。

一次只有一個執行緒可以佔有寫模式的讀寫鎖, 但是可以有多個執行緒同時佔有讀模式的讀寫鎖,可以提高併發性。

(1)、當讀寫鎖是寫加鎖狀態時, 在這個鎖被解鎖之前, 所有試圖對這個鎖加鎖的執行緒都會被阻塞。

(2)、當讀寫鎖在讀加鎖狀態時, 所有試圖以讀模式對它進行加鎖的執行緒都可以得到訪問權, 但是如果執行緒希望以寫模式對此鎖進行加鎖, 它必須阻塞知道所有的執行緒釋放鎖。

(3)、通常, 當讀寫鎖處於讀模式鎖住狀態時, 如果有另外執行緒試圖以寫模式加鎖, 讀寫鎖通常會阻塞隨後的讀模式鎖請求, 這樣可以避免讀模式鎖長期佔用, 而等待的寫模式鎖請求長期阻塞。

實用場景:讀寫鎖適合於對資料結構的讀次數比寫次數多得多的情況。 因為, 讀模式鎖定時可以共享, 以寫模式鎖住時意味著獨佔, 所以讀寫鎖又叫共享-獨佔鎖.
// Lock 將 rw 設定為寫鎖定狀態,禁止其他例程讀取或寫入。
func (rw *RWMutex) Lock()

// Unlock 解除 rw 的寫鎖定狀態,如果 rw 未被寫鎖定,則該操作會引發 panic。
func (rw *RWMutex) Unlock()

// RLock 將 rw 設定為讀鎖定狀態,禁止其他例程寫入,但可以讀取。
func (rw *RWMutex) RLock()

// Runlock 解除 rw 的讀鎖定狀態,如果 rw 未被讀鎖頂,則該操作會引發 panic。
func (rw *RWMutex) RUnlock()

// RLocker 返回一個互斥鎖,將 rw.RLock 和 rw.RUnlock 封裝成了一個 Locker 介面。
func (rw *RWMutex) RLocker() Locker

組等待

  WaitGroup 用於等待一組例程的結束。主例程在建立每個子例程的時候先呼叫 Add 增加等待計數,每個子例程在結束時呼叫 Done 減少例程計數。之後,主例程通過 Wait 方法開始等待,直到計數器歸零才繼續執行。

// 計數器增加 delta,delta 可以是負數。
func (wg *WaitGroup) Add(delta int)

// 計數器減少 1
func (wg *WaitGroup) Done()

// 等待直到計數器歸零。如果計數器小於 0,則該操作會引發 panic。
func (wg *WaitGroup) Wait()

條件變數

  條件等待當條件不滿足時通過 Wait 函式讓一個例程等待,另一個執行緒改變條件並通過Signal 傳送訊號讓一個等待的例程繼續,通過 Broadcast 讓所有等待的例程繼續。

  在 Wait 之前應當手動為 c.L 上鎖,Wait 結束後手動解鎖。為避免虛假喚醒,需要將 Wait 放到一個條件判斷迴圈中。官方要求的寫法如下:
c.L.Lock()
for !condition() {
    c.Wait()
}
// 執行條件滿足之後的動作...
c.L.Unlock()

Cond 在開始使用之後,不能再被複制。
type Cond struct {
    L Locker // 在“檢查條件”或“更改條件”時 L 應該鎖定。
} 

// 建立一個條件等待    初始化互斥鎖變數,返回Cond變數
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.L
func (c *Cond) Wait()

程式示例:

// 示例:條件等待
func main() {
	condition := false // 條件不滿足
	var mu sync.Mutex  //                       鎖 1、保護條件變數   2、 保護等待佇列
	cond := sync.NewCond(&mu)
	// 讓例程去創造條件
	go func() {
		mu.Lock()
		condition = true // 更改條件
		cond.Signal()    // 傳送通知:條件已經滿足
		mu.Unlock()
	}()
	mu.Lock()
	// 檢查條件是否滿足,避免虛假通知,同時避免 Signal 提前於 Wait 執行。
	for !condition {
		// 等待條件滿足的通知,如果收到虛假通知,則迴圈繼續等待。   =======這裡wait函式的實現 會有解鎖操作,等待喚醒等待佇列中的執行緒
		cond.Wait() // 等待時 mu 處於解鎖狀態,喚醒時重新鎖定。   //wait函式實現  類似於  linux條件變數
	}
	fmt.Println("條件滿足,開始後續動作...")
	mu.Unlock()
}

// 輸出結果:
// 條件滿足,開始後續動作...

單次執行

  Once 的作用是多次呼叫但只執行一次,Once 只有一個方法,Once.Do(),向 Do 傳入一個函式,這個函式在第一次執行 Once.Do() 的時候會被呼叫,以後再執行 Once.Do() 將沒有任何動作,即使傳入了其它的函式,也不會被執行,如果要執行其它函式,需要重新建立一個 Once 物件。Once 可以安全的在多個例程中並行使用。

// 多次呼叫僅執行一次指定的函式 f
func (o *Once) Do(f func())

程式示例:

func main() {
	var once sync.Once
	onceBody := 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
	}
}

Map型別

Map型別跟Go語言中 map [interface {}] interface {}類似,但是對於多個goroutine併發使用而不需要額外的鎖定或同步是安全的。Map型別是定製的,主要是保證單獨使用時保證併發執行的型別安全性。

Map型別針對兩種常見使用情況進行了優化:
(1)給定鍵的入口只寫入一次,但多次讀取,僅僅在快取中存在;

(2)在多個goroutine讀取,寫入和 覆蓋不相交鍵集的條目。 

在這兩種情況下,與使用單獨互斥或RWMutex鎖配對的Go型別相比,使用Map型別可以顯著減少鎖競爭。

//Map型別
type Map struct {
        // contains filtered or unexported fields
}

//刪除key的value值
func (m *Map) Delete(key interface{})

//讀取
func (m *Map) Load(key interface{}) (value interface{}, ok bool)
// LoadOrStore returns the existing value for the key if present.
// Otherwise, it stores and returns the given value.
// The loaded result is true if the value was loaded, false if stored
func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)
//通過匿名函式f迴圈處理Map的鍵值對,返回false 就停止
func (m *Map) Range(f func(key, value interface{}) bool)
//寫入
func (m *Map) Store(key, value interface{})