Go Mutex 互斥鎖

Gary Liao
4 min readJun 6, 2023

--

Mutual Exclusion 互斥鎖

Mutual Exclusion (Mutex),中文翻為互斥鎖,雖然可以透過 channel 達到這個功能,但 Go 官方提供了 sync.Mutex 以利使用。

sync.Mutex

var mu, mu1, mu2 sync.Mutex

// 不用初始化就能用了
mu.lock()
mu.unlock()

// 很少用,用了表示有深層的問題
mu.trylock()

假如宣告了一個mutex mu ,那麼所有使用 mu 的 function 都會互斥,只要其中一個執行了 mu.lock(),所有其他使用 mu 的 function都會被block,直到 mu.unlock(),也不一定會在同一個 function 裡面執行 mu.unlock()。其他沒有使用到 mu 的function 則不受影響。

舉例

經典案例就是存取款。下例中,存取款的動作會在短時間內利用 goroutine 頻繁發生,順序不定,但執行結果會依照完成時序丟入channel,最後依序印出。

package main

import (
"fmt"
"math/rand"
"sync"
"time"
)

var (
mu sync.Mutex // guards balance
balance int
channel chan string
)

func Deposit(amount int) {
mu.Lock()
defer mu.Unlock()
b1 := balance
balance += amount
time.Sleep(time.Millisecond * 10)
b2 := balance

if amount < 0 {
channel <- fmt.Sprintf("%d - %d = %d\n", b1, -amount, b2)
} else {
channel <- fmt.Sprintf("%d + %d = %d\n", b1, amount, b2)
}
}

func Balance() {
mu.Lock()
defer mu.Unlock()
b := balance
time.Sleep(time.Millisecond * 10)
channel <- fmt.Sprintf("%d \n", b)
}

func main() {
balance = 100
channel = make(chan string, 10)

// 產生10筆金額隨機的存款或取款 (-1 ~ 2),若0就是查詢餘額
moneys := make([]int, 10)
for i := 0; i < 10; i++ {
moneys[i] = rand.Intn(4) - 1
}
// 執行ATM操作: 存款、取款、查詢餘額,順序隨機
for _, money := range moneys {
switch {
case money == 0:
go Balance()
default:
go Deposit(money)
}

}
// 依ATM操作實際執行順序印出結果
for range moneys {
msg := <-channel
fmt.Print(msg)
}

}

執行結果

100 
100 - 1 = 99
99 + 1 = 100
100
100 - 1 = 99
99 - 1 = 98
98
98 + 1 = 99
99 + 2 = 101
101 + 2 = 103

如果把所有 mu.Lock()以及 defer mu.Unlock() 拿掉,結果所有加減法與當下餘額都是錯的:

104 + 1 = 105
102
100 + 1 = 105
101 + 2 = 105
103 - 1 = 105
102
102 + 1 = 105
102
104
103 + 1 = 105

— — —

如果只是多次查詢餘額,應該是不用彼此限制讀取,因此為了整體效能,有 multiple readers single writer lock,Go提供了 sync.RWMutex 來實現這種鎖。

sync.RWMutex

var mu sync.RWMutex

mu.lock()
mu.unlock()
mu.RLock()
mu.RUnlock()

// 還有更多,我還沒參透

執行 執行 mu.lock() 的function 依然會 block 執行 mu.RLock() 的function,但後者彼此之間不會互相 block,可以同時執行。因此上面的範例可以如下修改,那麼查詢餘額的動作就不會彼此block:

var (
mu sync.RWMutex

// ...

func Balance() {
mu.RLock()
defer mu.RUnlock()

// ...

--

--