傳統的並發對於共享資源的保護機制就是加上一把互斥鎖,當其中一個線程在訪問資源時將之上鎖,不允許其他線程訪問。
在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之角色互換,範例代碼的執行結果不存在上述兩種可能性以外的情況。
由於互斥鎖上鎖後,多個 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")
}
唯一鎖也是基於 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)
}