go channel ->同步

codestacklinuxer發表於2024-05-11

通道並非用來取代鎖,各有不同使用場景。
通道解決高階別邏輯層次併發架構,鎖則用來保護低階別區域性程式碼安全。

竟態條件:多執行緒同時讀寫共享資源(竟態資源)。
臨界區:讀寫竟態資源的程式碼片段。

互斥鎖:同一時刻,只有一個執行緒能進入臨界區。
讀寫鎖:寫獨佔(其他讀寫均被阻塞),讀共享。
訊號量:允許指定數量執行緒進入臨界區。
自旋鎖:失敗後,以迴圈積極嘗試。(無上下文切換,小粒度)

悲觀鎖:操作前獨佔鎖定。
樂觀鎖:假定無競爭,後置檢查。(Lock Free, CAS)

標準庫 sync 提供了多種鎖,另有原子操作等。

Mutex:互斥鎖。
RWMutex:讀寫鎖。

WaitGroup:等待一組任務結束。
Cond:單播或廣播喚醒其他任務。
Once:確保只呼叫一次(函式)。

Map:併發安全字典,(少寫多讀,資料不重疊)
Pool:物件池。(快取物件可被回收)

競爭檢測

測試階段,以 -race 編譯,注入競爭檢查(data race detection)指令。

有較大效能損失,避免在基準測試和釋出版本中使用。
有不確定性,不能保證百分百測出。
單元測試有效完整,定期執行競爭檢查。

條件變數

內部以計數器和佇列作為單播(signal)和廣播(broadcast)依據。
引入外部鎖作為竟態資源保護,可與其他邏輯同步。

func main() {
	var wg sync.WaitGroup

	cond := sync.NewCond(&sync.Mutex{})
	data := make([]int, 0)

	// 1 寫
	wg.Add(1)
	go func() {
		defer wg.Done()

		for i := 0; i < 5; i++ {
            
            // 保護竟態資源。
			cond.L.Lock()
			data = append(data, i + 100)
			cond.L.Unlock()
            
            // 喚醒一個。
			cond.Signal()
		}

        // 喚醒所有(剩餘)。
		// cond.Broadcast()
	}()

	// n 讀
	for i := 0; i < 5; i++ {
		wg.Add(1)

		go func(id int) {
			defer wg.Done()
            
            // 鎖定竟態資源。
			cond.L.Lock()

            // 迴圈檢查是否符合後續操作條件。
            // 如條件不符,則繼續等待。
			for len(data) == 0 {
				cond.Wait()
			}

			x := data[0]
			data = data[1:]

			cond.L.Unlock()
			println(id, ":", x)
		}(i)
	}

	wg.Wait()
}

相關文章