介紹Golang互斥鎖

介紹Golang互斥鎖

使用Go實現高度並行程序並不妨礙你實現具有競爭條件特性的應用。競爭條件會引起應用出現不期望的問題,且很難調試和修復。因此我們需要使用Go實現在安全方式下實現並行應用,並且不影響性能,這就是互斥鎖要發揮的作用。

1. 互斥鎖

互斥鎖(Mutex)是一種機制,它阻止併發進程進入一個臨界數據段,而該臨界數據段已經被給定進程佔用。

下面使用現實示例進行說明。我們有一個銀行賬戶,應用系統提供對賬戶實現存款和取款功能。在單線程同步程序這很容易實現,只需要必要單元測試就可以有效地保障程序按照預期需求執行。但如果在多線程或協程場景下,我們的代碼可能會遇到問題:

  1. 假設客戶賬號有1000元
  2. 客戶往賬號中存款500元
  3. 一個協程處理該事務,讀賬號獲得餘額1000,然後給該賬號增加500
  4. 同時,另一個應用需要付700元按揭貸款
  5. 第二個協程在第一個程序增加500元存款之前讀賬號獲得餘額1000,然後給該賬號扣除700元
  6. 客戶第二天檢查銀行賬號餘額,發現僅剩300元。因爲第二個協程並不知道第一個協程存款,重寫了存款金額。

顯然客戶無法接受無緣無故的損失,這就是競爭條件下造成異常結果。

2. 示例

現在我們知道了問題緣由,讓我們使用mutex修復該問題。爲了使用互斥鎖,需要導入sync包。

package main

import (
    "fmt"
    "sync"
)

var (
    mutex   sync.Mutex
    balance int
)

func init() {
    balance = 1000
}

func deposit(value int, wg *sync.WaitGroup) {
    mutex.Lock()
    fmt.Printf("Depositing %d to account with balance: %d\n", value, balance)
    balance += value
    mutex.Unlock()
    wg.Done()
}

func withdraw(value int, wg *sync.WaitGroup) {
    mutex.Lock()
    fmt.Printf("Withdrawing %d from account with balance: %d\n", value, balance)
    balance -= value
    mutex.Unlock()
    wg.Done()
}

func main() {
    fmt.Println("Go Mutex Example")

	var wg sync.WaitGroup
	wg.Add(2)
    go withdraw(700, &wg)
    go deposit(500, &wg)
    wg.Wait()

    fmt.Printf("New Balance %d\n", balance)
}

我們開始分析上面程序。 在deposit()和 withdraw()函數裏,首先都通過mutex.Lock()獲得mutex,每個函數都被阻塞直到獲得鎖,一旦獲得鎖,纔可以進入競爭條件部分執行操作,讀信息,接着更新餘額。每個函數執行完相應操作通過mutex.Unlock()方法釋放鎖。執行上述示例輸出結果:

Go Mutex Example
Depositing 500 to account with balance: 1000
Withdrawing 700 from account with balance: 1500
New Balance 800

提示:Go中能夠安全地併發訪問數組嗎?當然不行,Go中併發讀寫訪問都不安全,需要手動使用mutex保障。

3. 避免死鎖

當使用互斥鎖時,很多時候需要注意死鎖。死鎖是代碼不能執行,每個線程或協程都被阻塞等待獲得鎖。

  • 確保調用Unlock()方法

如果正在開發的協程需要鎖,其中有很多種方式終止,無論哪種方式終止都要確保協程調用Unlock()方法。如果調用Unlock()方法時有錯誤,程序可能會進入死鎖,因爲其他程序無法獲得互斥鎖。

  • 調用 Lock() 兩次

在應用中使用互斥鎖時要注意,Lock()方法會阻塞,需要確保在你開發的應用中不要對同一個鎖調用Lock()方法兩次,這會導致死鎖。

package main

import (
	"fmt"
	"sync"
)

func main() {
	var b sync.Mutex
	
	b.Lock()
	b.Lock()
	fmt.Println("This never executes as we are in deadlock") 
}

執行程序會地得錯誤輸出:

fatal error: all goroutines are asleep - deadlock!

...

4. 總結

本文介紹了競爭條件下如何實現並行應用,並通過示例分析並行應用中造成非預期的問題以及如何通過互斥鎖實現安全的並行程序。希望對你學習有點幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章