1 回答

TA貢獻(xiàn)1155條經(jīng)驗(yàn) 獲得超0個(gè)贊
synchronized
在 Java 中是一種只允許單個(gè)線程執(zhí)行代碼塊的方法(在任何給定時(shí)間)。
在 Go 中有許多結(jié)構(gòu)可以實(shí)現(xiàn)這一點(diǎn)(例如互斥鎖、通道、等待組、原語sync/atomic
),但 Go 的格言是:“不要通過共享內(nèi)存進(jìn)行通信;相反,通過通信共享內(nèi)存?!?/em>
因此,與其鎖定和共享變量,不如嘗試不這樣做,而是在 goroutine 之間傳遞結(jié)果,例如使用通道(這樣您就不必訪問共享內(nèi)存)。
當(dāng)然,在某些情況下,最簡單、直接的解決方案是使用互斥鎖來保護(hù)多個(gè) goroutine 對變量的并發(fā)訪問。在這種情況下,您可以這樣做:
var (
? ? mu? ? ? ? sync.Mutex
? ? protectMe int
)
func getMe() int {
? ? mu.Lock()
? ? me := protectMe
? ? mu.Unlock()
? ? return me
}
func setMe(me int) {
? ? mu.Lock()
? ? protectMe = me
? ? mu.Unlock()
}
上述解決方案可以在幾個(gè)方面進(jìn)行改進(jìn):
使用
sync.RWMutex
instead of?sync.Mutex
,以便getMe()
可以鎖定為只讀,這樣多個(gè)并發(fā)讀者就不會互相阻塞。在(成功)鎖定之后,建議使用 解鎖
defer
,這樣如果后續(xù)代碼中發(fā)生錯(cuò)誤(例如運(yùn)行時(shí)恐慌),互斥體仍將被解鎖,避免資源泄漏和死鎖。盡管此示例非常簡單,但不會發(fā)生任何不良情況,也不能保證無條件地使用延遲解鎖。最好讓互斥體靠近它應(yīng)該保護(hù)的數(shù)據(jù)。因此,“包裝”
protectMe
及其mu
在結(jié)構(gòu)中是個(gè)好主意。而且如果我們這樣做,我們也可以使用嵌入,因此鎖定/解鎖變得更加方便(除非必須不公開此功能)。
因此,上述示例的改進(jìn)版本可能如下所示(在Go Playground上嘗試):
type Me struct {
? ? sync.RWMutex
? ? me int
}
func (m *Me) Get() int {
? ? m.RLock()
? ? defer m.RUnlock()
? ? return m.me
}
func (m *Me) Set(me int) {
? ? m.Lock()
? ? m.me = me
? ? m.Unlock()
}
var me = &Me{}
func main() {
? ? me.Set(2)
? ? fmt.Println(me.Get())
}
此解決方案還有另一個(gè)優(yōu)點(diǎn):如果您需要 的多個(gè)值Me,它會自動為每個(gè)值具有不同的、單獨(dú)的互斥鎖(我們的初始解決方案需要為每個(gè)新值手動創(chuàng)建單獨(dú)的互斥鎖)。
雖然這個(gè)例子是正確有效的,但可能不實(shí)用。因?yàn)楸Wo(hù)單個(gè)整數(shù)并不真正需要互斥體。我們可以使用sync/atomic包實(shí)現(xiàn)相同的目的:
var protectMe int32
func getMe() int32 {
? ? return atomic.LoadInt32(&protectMe)
}
func setMe(me int32) {
? ? atomic.StoreInt32(&protectMe, me)
}
這個(gè)解決方案更短、更干凈、更快。如果您的目標(biāo)只是保護(hù)單個(gè)值,則首選此解決方案。如果您應(yīng)該保護(hù)的數(shù)據(jù)結(jié)構(gòu)更復(fù)雜,atomic
甚至可能不可行,那么使用互斥量可能是合理的。
現(xiàn)在在展示了共享/保護(hù)變量的例子之后,我們還應(yīng)該給出一個(gè)例子,我們應(yīng)該實(shí)現(xiàn)什么目標(biāo)來實(shí)現(xiàn)“不要通過共享內(nèi)存進(jìn)行通信;相反,通過通信共享內(nèi)存”。
情況是你有多個(gè)并發(fā)的 goroutines,你使用一個(gè)變量來存儲一些狀態(tài)。一個(gè) goroutine 更改(設(shè)置)狀態(tài),另一個(gè) goroutine 讀?。ǐ@?。顟B(tài)。要從多個(gè) goroutine 訪問此狀態(tài),必須同步訪問。
這個(gè)想法是不要有這樣的“共享”變量,而是一個(gè) goroutine 設(shè)置的狀態(tài),它應(yīng)該“發(fā)送”它,而另一個(gè) goroutine 會讀取它,它應(yīng)該是狀態(tài)“發(fā)送到”(或者換句話說,另一個(gè) goroutine 應(yīng)該接收更改后的狀態(tài))。所以沒有共享狀態(tài)變量,取而代之的是2 個(gè) goroutines 之間的通信。Go 為這種“協(xié)程間”通信提供了出色的支持:channels。對通道的支持內(nèi)置于語言中,有send statements,receive operators和其他支持(例如你可以循環(huán)在通道上發(fā)送的值)。
讓我們看一個(gè)實(shí)際/現(xiàn)實(shí)生活中的例子:“經(jīng)紀(jì)人”。代理是一個(gè)實(shí)體,其中“客戶端”(goroutines)可以訂閱接收消息/更新,并且代理能夠向訂閱的客戶端廣播消息。在一個(gè)有大量客戶端隨時(shí)可能訂閱/取消訂閱的系統(tǒng)中,并且可能需要隨時(shí)廣播消息,以安全的方式同步所有這些將很復(fù)雜。明智地使用通道,這個(gè)代理實(shí)現(xiàn)相當(dāng)干凈和簡單。該實(shí)現(xiàn)對于并發(fā)使用是完全安全的,支持“無限”客戶端,并且不使用單個(gè)互斥鎖或共享變量,僅使用通道。
- 1 回答
- 0 關(guān)注
- 128 瀏覽
添加回答
舉報(bào)