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