首頁 > 軟體

GO中sync包自由控制並行範例詳解

2022-08-04 22:05:00

資源競爭

channel 常用於並行通訊,要保證並行安全,主要使用互斥鎖。在並行的過程中,當一個記憶體被多個 goroutine 同時存取時,就會產生資源競爭的情況。這塊記憶體也可以稱為共用資源。

並行時對於共用資源必然會出現搶佔資源的情況,如果是對某資源的統計,很可能就會導致結果錯誤。為保證只有一個協程拿到資源並操作它,可以引入互斥鎖 sync.Mutex。

sync.Mutex

互斥鎖,指的是並行時,在同一時刻只有一個協程執行某段程式碼,其他協程只有等待該協程執行完成後才能繼續執行。

var (sum int 
 mutex sync.Mutex)
func add(i int){
    mutex.Lock()
    sum+=i
    mute.Unlock()
}

使用 mutex 加鎖保護 sum+ =i 的程式碼片段,這樣這個片段區就無法同時被多個協程存取,當有協程進入該片段區,那其他的協程就只有等待,以此保證臨界區的並行安全。

sync.Mutex 只有 Lock()和 Unlock() 方法,它們是成對存在的,且Lock後一定要執行Unlock進行釋放鎖。所以可以使用 defer 語句釋放鎖,以保證鎖一定會被釋放。

func add(i int){
    mutex.Lock()
    defer mutex.Unlock()
    sum += i
}

sync.RWMutex

上面例子是對 sum 寫操作時使用sync.Mutex 保證並行安全,當多個協程進行讀取操作時,避免因並行產生讀取資料不正確,也是可以使用互斥鎖 sync.Mutex。

func getSum(){
    mutex.Lock()
    defer mutex.Unlock()
    b:=sum
    return b
}

多個協程 goroutine 同時讀寫的資源競爭問題解決,還需要考慮效能問題,每次讀寫共用資源都加鎖,也會導致效能低。

多個協程並行進行讀寫時會遇到以下情況:

  • 寫時不能同時讀,易讀到髒資料
  • 讀時不能同時寫,因為會導致結果不一致
  • 讀時同時寫,因資料不變,無論多少個 goroutine 讀都是並行安全

使用讀寫鎖 sync.RWMutex 優化程式碼:

var mutex sync.RWMutex
func readSum() int {
    mutex.RLock()
    defer mutex.RUnlock()
    b := sum
    return b
}

讀寫鎖的效能比互斥鎖效能好。

sync.WaitGroup

為了能夠監聽所有的協程的執行,一旦所有的goroutine 都執行完成,程式應當及時退出節省時間提高效能。通過使用 sync.WaitGroup 來解決。使用步驟如下:

  • 宣告一個 sync.WaitGroup ,通過 add 方法增加計數器值,有幾個協程就計算幾個
  • 每個協程結束後就呼叫 Done 方法,主要是讓計數器減1
  • 最後呼叫 Wait 方法一直等待,直到計數器為 0 時,所有跟蹤的協程都執行完畢
func run() {
    var wg sync.WaitGroup
    wg.Add(100)
    for i := 0; i < 100; i++ {
        go func() {
            defer wg.Done()
            add(10)
        }()
    }
    wg.Wait()
}

通過 sync.WaitGroup 可以很好地跟蹤協程.

sync.Once

sync.Once 作用是讓程式碼只執行一次。詳細使用是呼叫方法 once.Do 方法,具體實現:

func main(){
    var once sync.once
    oneFunc := func(){
        println("once func")
    }
    once.Do(oneFunc)
}

sync.Once 適用於建立某個物件的單例、只載入一次的資源等只執行一次的場景。

sync.Cond

使用 sync.WaitGroup 主要是控制等待所有的協程都執行完畢,才最終完成。但是當遇到場景是,只有等待所有條件都準備好才開始。sync.Cond 相當於發號施令,只有通知執行所有的協程才可以執行,重點是所有協程需等待喚醒才可以開始。

所以 sync.Cond 具有阻塞協程和喚醒協程的功能。詳細的用法:

  • 通過 sync.NewCond 函數生成一個 *sync.Cond,用於阻塞和喚醒協程
  • 呼叫 cond.Wait() 方法阻塞當前協程等待,需要注意呼叫 cond.Wait() 方法要加鎖
  • 呼叫 cond.Broadcast() 後所有協程才開始執行
func run() {
    cond := sync.NewCond(&amp;sync.Mutex{})
    var wg sync.WaitGroup
    wg.Add(101)
    for i := 0; i &lt; 100; i++ {
        go func(num int) {
            defer wg.Done()
            fmt.Println(num, "號正在 awaiting......")
            cond.L.Lock()
            cond.Wait() //等待所有協程準備完成
            fmt.Println(num, "號開始跑……")
            cond.L.Unlock()
        }(i)
    }
    // 等待所有的協程都進入 wait 狀態
    time.Sleep(2*time.Second)
    go func() {
        defer wg.Done()
        // 所有都準備完成,開始
        cond.Broadcast()
    }()
    // 防止函數提前返回退出
    wg.Wait()
}

以上就是GO中sync包自由控制並行範例詳解的詳細內容,更多關於GO sync包控制並行的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com