在一些復(fù)雜的程序中,通常通過不同線程執(zhí)行不同應(yīng)用來實(shí)現(xiàn)程序的并發(fā)。當(dāng)不同線程要使用同一個(gè)變量時(shí),經(jīng)常會(huì)出現(xiàn)一個(gè)問題:無法預(yù)知變量被不同線程修改的順序!(這通常被稱為資源競爭,指不同線程對(duì)同一變量使用的競爭)顯然這無法讓人容忍,那我們?cè)撊绾谓鉀Q這個(gè)問題呢?
經(jīng)典的做法是一次只能讓一個(gè)線程對(duì)共享變量進(jìn)行操作。當(dāng)變量被一個(gè)線程改變時(shí)(臨界區(qū)),我們?yōu)樗湘i,直到這個(gè)線程執(zhí)行完成并解鎖后,其他線程才能訪問它。
特別是我們之前章節(jié)學(xué)習(xí)的 map 類型是不存在鎖的機(jī)制來實(shí)現(xiàn)這種效果(出于對(duì)性能的考慮),所以 map 類型是非線程安全的。當(dāng)并行訪問一個(gè)共享的 map 類型的數(shù)據(jù),map 數(shù)據(jù)將會(huì)出錯(cuò)。
在 Go 語言中這種鎖的機(jī)制是通過 sync 包中 Mutex 來實(shí)現(xiàn)的。sync 來源于 "synchronized" 一詞,這意味著線程將有序的對(duì)同一變量進(jìn)行訪問。
sync.Mutex
是一個(gè)互斥鎖,它的作用是守護(hù)在臨界區(qū)入口來確保同一時(shí)間只能有一個(gè)線程進(jìn)入臨界區(qū)。
假設(shè) info 是一個(gè)需要上鎖的放在共享內(nèi)存中的變量。通過包含 Mutex
來實(shí)現(xiàn)的一個(gè)典型例子如下:
import "sync"
type Info struct {
mu sync.Mutex
// ... other fields, e.g.: Str string
}
如果一個(gè)函數(shù)想要改變這個(gè)變量可以這樣寫:
func Update(info *Info) {
info.mu.Lock()
// critical section:
info.Str = // new value
// end critical section
info.mu.Unlock()
}
還有一個(gè)很有用的例子是通過 Mutex 來實(shí)現(xiàn)一個(gè)可以上鎖的共享緩沖器:
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
在 sync 包中還有一個(gè) RWMutex
鎖:他能通過 RLock()
來允許同一時(shí)間多個(gè)線程對(duì)變量進(jìn)行讀操作,但是只能一個(gè)線程進(jìn)行寫操作。如果使用 Lock()
將和普通的 Mutex
作用相同。包中還有一個(gè)方便的 Once
類型變量的方法 once.Do(call)
,這個(gè)方法確保被調(diào)用函數(shù)只能被調(diào)用一次。
相對(duì)簡單的情況下,通過使用 sync 包可以解決同一時(shí)間只能一個(gè)線程訪問變量或 map 類型數(shù)據(jù)的問題。如果這種方式導(dǎo)致程序明顯變慢或者引起其他問題,我們要重新思考來通過 goroutines 和 channels 來解決問題,這是在 Go 語言中所提倡用來實(shí)現(xiàn)并發(fā)的技術(shù)。我們將在第 14 章對(duì)其深入了解,并在第 14.7 節(jié)中對(duì)這兩種方式進(jìn)行比較。