使用 sync.Cond 來協調并發 goroutine 的訪問共享資源
使用 sync.Cond 解決并發訪問共享資源問題
在并發編程中,當多個 goroutine 需要訪問共享資源時,我們需要使用一些機制來協調它們的執行順序,以避免競態條件和數據不一致的問題。在 Go 語言中,sync.Cond 條件變量就是一種常用的機制,它可以用來等待和通知其他 goroutine。
sync.Cond 和互斥鎖的區別
互斥鎖(sync.Mutex)用于保護臨界區和共享資源,而 sync.Cond 則用于協調多個 goroutine 的執行順序。互斥鎖只能一個 goroutine 持有鎖,其他 goroutine 必須等待鎖被釋放才能繼續執行。而 sync.Cond 可以讓等待的 goroutine 在條件滿足時被喚醒,進而繼續執行。
sync.Cond 的四個方法
sync.Cond 的定義如下:
// 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
// L is held while observing or changing the condition
L Locker
notify notifyList
checker copyChecker
}
每個 Cond 實例都會關聯一個鎖 L(互斥鎖 *Mutex,或讀寫鎖 *RWMutex),當修改條件或者調用 Wait 方法時,必須加鎖。
1. NewCond 創建實例
func NewCond(l Locker) *Cond
NewCond 方法用于創建一個 Cond 實例,并關聯一個鎖(互斥鎖或讀寫鎖)。
2. Broadcast 廣播喚醒所有等待的 goroutine
// 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()
Broadcast 方法用于喚醒所有等待條件變量 c 的 goroutine。它不需要持有鎖來調用。
3. Signal 喚醒一個等待的 goroutine
// 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()
Signal 方法用于喚醒一個等待條件變量 c 的 goroutine。它不需要持有鎖來調用。
4. Wait 等待條件變量滿足
// 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()
Wait 方法會自動釋放鎖,并掛起當前的 goroutine,直到條件變量 c 被 Broadcast 或 Signal 喚醒。被喚醒后,Wait 方法會重新獲得鎖,并繼續執行后續的代碼。
使用示例
下面是一個使用 sync.Cond 的示例,實現了一個簡單的讀寫同步機制:
package main
import (
"fmt"
"sync"
"time"
)
var done = false
func read(str string, c *sync.Cond) {
c.L.Lock()
for !done {
c.Wait()
}
fmt.Println(str, "start reading")
c.L.Unlock()
}
func write(str string, c *sync.Cond) {
fmt.Println(str, "start writing")
time.Sleep(2 * time.Second)
c.L.Lock()
done = true
c.L.Unlock()
fmt.Println(str, "wake up all")
c.Broadcast()
}
func main() {
m := &sync.Mutex{}
c := sync.NewCond(m)
go read("reader1", c)
go read("reader2", c)
write("writer", c)
time.Sleep(5 * time.Second)
}
在這個示例中,有兩個讀取協程(reader1 和 reader2)和一個寫入協程(writer)。寫入協程在執行后會通知所有等待的讀取協程,讀取協程在條件滿足時才能開始讀取。
輸出結果如下:
writer start writing
writer wake up all
reader2 start reading
reader1 start reading
通過使用 sync.Cond,我們可以很方便地實現多個 goroutine 之間的等待和通知機制,從而更好地協調并發訪問共享資源的執行順序。