iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 25
0

Mutex 互斥鎖

定義

傳統的並發對於共享資源的保護機制就是加上一把互斥鎖,當其中一個線程在訪問資源時將之上鎖,不允許其他線程訪問。

sync.Mutex 使用

在Go語言裡,互斥鎖由標準庫 sync.Mutex 所實現,它定義了一個 Mutex 的結構體,其擁有兩個方法 Lock() 和 Unlock()

type Mutex struct {
    state int32
    sema  uint32
}

func (m *Mutex) Lock()

func (m *Mutex) Unlock()

sync.Mutex的零值表示未被鎖定的互斥量。

當一個變數被上了互斥鎖時,其他的線程訪問該變數時會被堵塞,直到鎖被釋放為止。如果對一個已上鎖的變數重複上鎖會引起 panic ,因此建議上鎖和解鎖的動作存在於同一個函數裡頭。

舉例

下面是一個互斥鎖的例子, main 函數裡啟用了兩個 goroutine 調用 read 函數,不管是 goroutine1 或 goroutine2 哪一個先獲得互斥鎖,先上鎖的 goroutine 會堵塞另一個 goroutine 。

package main

import (
    "fmt"
    "sync"
    "time"
)

var m *sync.Mutex

func main() {
    m = new(sync.Mutex)
    go read(1)
    go read(2)
    time.Sleep(time.Second) // 让goroutine有足够的时间执行完
}

func read(i int) {
    fmt.Println(i, "begin lock")
    m.Lock()
    fmt.Println(i, "in lock")
    m.Unlock()
    fmt.Println(i, "unlock")
}

假設 goroutine1 先獲得互斥鎖,則輸出為:

1 begin lock // goroutine1 獲得互斥鎖
2 begin lock // goroutine2 未獲得互斥鎖,因此被堵塞
1 in lock
1 unlock
2 in lock    // 互斥鎖釋放後,喚醒 goroutine2
2 unlock

若是 goroutine2 先獲得互斥鎖,則上述的輸出內容 1、2之角色互換,範例代碼的執行結果不存在上述兩種可能性以外的情況。

RWMutex 讀寫鎖

定義

由於互斥鎖上鎖後,多個 goroutine 並發執行時永遠只有一個擁有互斥鎖的 goroutine 可以執行, 其他的 goroutine 被堵塞,這種結果導致了並發執行的效能低落,因此Go語言實現了另一種鎖來提升程式的並發效率 - 讀寫鎖 RWMutex。

讀寫鎖是基於互斥鎖的原理實現的,讀寫鎖同樣擁有上鎖 Lock() 和解鎖 Unlock() 的方法,並且進一步將鎖升級為寫鎖和讀鎖。

一個變數被上寫鎖時,效果和上互斥鎖一樣,只有擁有鎖的 goroutine 才能對該變數讀寫。若是變數被上讀鎖,數據可以被多個 goroutine 訪問讀取,但是不可寫。

使用

type RWMutex struct {
    w           Mutex  // held if there are pending writers
    writerSem   uint32 // semaphore for writers to wait for completing readers
    readerSem   uint32 // semaphore for readers to wait for completing writers
    readerCount int32  // number of pending readers
    readerWait  int32  // number of departing readers
}

func (*RWMutex) Lock    // 寫鎖

func (*RWMutex) Unlock

func (*RWMutex) RLock   // 讀鎖

func (*RWMutex) RUnlock

範例

package main

import (
	"fmt"
	"sync"
	"time"
)

var m *sync.RWMutex
var val = 0

func main() {
	m = new(sync.RWMutex)
	go read(1)
	go write(2)
	go read(3)
	time.Sleep(5 * time.Second)
}

func read(i int) {
	fmt.Println(i, "begin read")
	m.RLock()
	time.Sleep(1 * time.Second)
	fmt.Println(i, "val: ", val)
	time.Sleep(1 * time.Second)
	m.RUnlock()
	fmt.Println(i, "end read")
}

func write(i int) {
	fmt.Println(i, "begin write")
	m.Lock()
	val = 10
	fmt.Println(i, "val: ", val)
	time.Sleep(1 * time.Second)
	m.Unlock()
	fmt.Println(i, "end write")
}

sync.Once 唯一鎖

定義

唯一鎖也是基於 Mutex 實現的一個鎖,使用的情境是有一個函數,我們希望在並發執行時只會被所有的 goroutine 總共執行一次,那麼我們就可以對該函數上唯一鎖。

使用

唯一鎖裡頭有一個 Do() 方法,以 Do 調用的函數在執行完後會被上鎖,其他的 goroutine不可再執行該函數。

type Once struct {
    m    Mutex
    done uint32
}

func (o *Once) Do(f func())

範例

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var once sync.Once
    for i := 0; i < 10; i++ {
        go func() {
            once.Do(read)
        }()
    }
    time.Sleep(time.Second)
}

func read() {
    fmt.Println(1)
}

上一篇
Day24 並發同步機制(2) - Channel
下一篇
Day26 同步問題 - 死鎖 與 哲學家就餐問題
系列文
Golang入門到進階實戰30

尚未有邦友留言

立即登入留言