Go語(yǔ)言并發(fā)編程臨界區(qū)的使用
臨界區(qū)是多線(xiàn)程/并發(fā)編程中的核心概念,指程序中訪問(wèn)共享資源(如變量、數(shù)據(jù)結(jié)構(gòu)、文件等)的代碼段,這些資源在同一時(shí)間只能被一個(gè)線(xiàn)程訪問(wèn)以避免數(shù)據(jù)競(jìng)爭(zhēng)和不一致。
本篇文章著重介紹臨界區(qū),鎖的詳細(xì)介紹會(huì)在下一篇文章中。
基本定義
臨界區(qū)是指:
- 訪問(wèn)共享資源的代碼片段
- 需要同步機(jī)制保護(hù)的部分
- 同一時(shí)間只允許一個(gè)執(zhí)行線(xiàn)程/goroutine進(jìn)入的區(qū)域
關(guān)鍵的特性:
- 共享資源訪問(wèn):涉及對(duì)共享內(nèi)存、文件、設(shè)備等資源的讀寫(xiě)操作
- 原子性需求:臨界區(qū)內(nèi)的操作應(yīng)作為一個(gè)不可分割的單元執(zhí)行
- 互斥/排他訪問(wèn):必須確保同一時(shí)間只有一個(gè)執(zhí)行流能進(jìn)入臨界區(qū)
- 有限停留:線(xiàn)程應(yīng)盡快離開(kāi)臨界區(qū),減少阻塞其他線(xiàn)程的時(shí)間
在Go中臨界區(qū)示例
無(wú)保護(hù)的臨界區(qū)(危險(xiǎn))
var counter int // 共享變量
func increment() {
counter++ // 這就是臨界區(qū)(沒(méi)有保護(hù))
}使用Mutex保護(hù)的臨界區(qū)
var (
counter int
mu sync.Mutex
)
func safeIncrement() {
mu.Lock() // 進(jìn)入臨界區(qū)前加鎖
defer mu.Unlock() // 確保退出時(shí)解鎖
counter++ // 受保護(hù)的臨界區(qū)
// 其他操作...
}臨界區(qū)與鎖的關(guān)系
| 概念 | 描述 |
|---|---|
| 臨界區(qū) | 需要保護(hù)的代碼段(概念) |
| 鎖 | 保護(hù)臨界區(qū)的實(shí)現(xiàn)機(jī)制(工具) |
| 關(guān)系 | 鎖用來(lái)劃定和保護(hù)臨界區(qū),臨界區(qū)是需要鎖保護(hù)的代碼范圍 |
臨界區(qū)的設(shè)計(jì)規(guī)范
1.最小化原則:
- 盡量減小臨界區(qū)的范圍
- 只包含必須同步的操作
// 不好:包含非必要操作 mu.Lock() data := fetchFromDatabase() // 耗時(shí)IO操作 sharedMap[key] = data mu.Unlock() // 更好:僅保護(hù)共享訪問(wèn) data := fetchFromDatabase() mu.Lock() sharedMap[key] = data mu.Unlock()
2.簡(jiǎn)短執(zhí)行
- 避免在臨界區(qū)內(nèi)執(zhí)行耗時(shí)操作如:IO、復(fù)雜計(jì)算
- 典型臨界區(qū)應(yīng)能在微秒級(jí)完成
3.單一職責(zé)
- 一個(gè)臨界去最好只保護(hù)一個(gè)共享資源
- 避免多個(gè)不相關(guān)資源共同用同一個(gè)鎖
4.無(wú)嵌套原則
- 避免在臨界區(qū)內(nèi)調(diào)用可能獲取其他鎖的方法
- 防止死鎖發(fā)生
臨界區(qū)保護(hù)機(jī)制對(duì)比
1. 互斥鎖(Mutex)
var mu sync.Mutex
func accessShared() {
mu.Lock()
// 臨界區(qū)...
mu.Unlock()
}2. 讀寫(xiě)鎖(RWMutex)
var rwMu sync.RWMutex
func readShared() {
rwMu.RLock()
// 只讀臨界區(qū)(允許多個(gè)讀者)
rwMu.RUnlock()
}
func writeShared() {
rwMu.Lock()
// 寫(xiě)臨界區(qū)(獨(dú)占)
rwMu.Unlock()
}3. 通道(Channel)
var ch = make(chan struct{}, 1) // 容量1的通道模擬鎖
func accessShared() {
ch <- struct{}{} // 獲取"鎖"
// 臨界區(qū)...
<-ch // 釋放"鎖"
}常會(huì)遇到的問(wèn)題
1. 數(shù)據(jù)競(jìng)爭(zhēng)(Data Race)
// 兩個(gè)goroutine并發(fā)執(zhí)行此函數(shù)會(huì)導(dǎo)致數(shù)據(jù)競(jìng)爭(zhēng)
func race() {
counter++ // 未保護(hù)的臨界區(qū)
}檢測(cè):使用go run -race或go test -race
2. 死鎖(Deadlock)
func deadlock() {
mu.Lock()
mu.Lock() // 重復(fù)加鎖導(dǎo)致死鎖
mu.Unlock()
mu.Unlock()
}3. 活鎖(Livelock)
// 兩個(gè)goroutine不斷重試但無(wú)法進(jìn)展
func livelock() {
for {
if mu.TryLock() { // Go 1.18+
// 臨界區(qū)...
mu.Unlock()
break
}
time.Sleep(time.Millisecond) // 可能導(dǎo)致活鎖
}
}項(xiàng)目案例改編
銀行賬戶(hù)轉(zhuǎn)賬
type Account struct {
mu sync.Mutex
balance int
}
func (a *Account) Transfer(to *Account, amount int) error {
// 按固定順序加鎖防止死鎖
first, second := a, to
if a < to { // 通過(guò)地址比較確定順序
first, second = to, a
}
first.mu.Lock()
defer first.mu.Unlock()
second.mu.Lock()
defer second.mu.Unlock()
// 臨界區(qū)開(kāi)始
if a.balance < amount {
return errors.New("insufficient balance")
}
a.balance -= amount
to.balance += amount
// 臨界區(qū)結(jié)束
return nil
}深度解讀:
- func (a *Account) Transfer(to *Account, amount int) error:定義了一個(gè)名為T(mén)ransfer的方法,該方法屬于Account類(lèi)型的接收者,表示從一個(gè)賬戶(hù)向另一個(gè)賬戶(hù)轉(zhuǎn)賬。方法接收兩個(gè)參數(shù),to是指向目標(biāo)賬戶(hù)的指針,amount是要轉(zhuǎn)賬的金額。返回值是error類(lèi)型,用于處理可能出現(xiàn)的錯(cuò)誤情況。
- 防止死鎖的機(jī)制:在進(jìn)行轉(zhuǎn)賬操作之前,首先對(duì)兩個(gè)賬戶(hù)進(jìn)行排序(通過(guò)比較兩個(gè)賬戶(hù)指針的內(nèi)存地址),確??偸窍孺i定地址較小的那個(gè)賬戶(hù)的互斥鎖,然后再鎖定地址較大的那個(gè)賬戶(hù)的互斥鎖。這種按固定順序加鎖的策略可以有效避免兩個(gè)或多個(gè) goroutine 同時(shí)嘗試鎖定不同賬戶(hù)的互斥鎖時(shí)出現(xiàn)的死鎖情況。
- 互斥鎖的使用:通過(guò)first.mu.Lock()和second.mu.Lock()分別鎖定兩個(gè)賬戶(hù)的互斥鎖,確保在同一時(shí)間只有一個(gè) goroutine 可以訪問(wèn)這兩個(gè)賬戶(hù)的余額。defer first.mu.Unlock()和defer second.mu.Unlock()語(yǔ)句用于確保在函數(shù)執(zhí)行完畢后,無(wú)論是否發(fā)生錯(cuò)誤,最終都能釋放這兩個(gè)賬戶(hù)的互斥鎖。
- 臨界區(qū):在兩個(gè)賬戶(hù)的互斥鎖都被成功鎖定之后,就開(kāi)始執(zhí)行轉(zhuǎn)賬操作的臨界區(qū)代碼。首先檢查轉(zhuǎn)出賬戶(hù)的余額是否足夠覆蓋轉(zhuǎn)賬金額,如果余額不足,則返回一個(gè)錯(cuò)誤信息"insufficient balance"。否則,從轉(zhuǎn)出賬戶(hù)扣除相應(yīng)的金額,并將該金額加到轉(zhuǎn)入賬戶(hù)的余額中
到此這篇關(guān)于Go語(yǔ)言并發(fā)編程臨界區(qū)的使用的文章就介紹到這了,更多相關(guān)Go語(yǔ)言 臨界區(qū)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
GO Cobra Termui庫(kù)開(kāi)發(fā)終端命令行小工具輕松上手
這篇文章主要為大家介紹了GO語(yǔ)言開(kāi)發(fā)終端命令行小工具,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01
基于HLS創(chuàng)建Golang視頻流服務(wù)器的優(yōu)缺點(diǎn)
HLS 是 HTTP Live Streaming 的縮寫(xiě),是蘋(píng)果開(kāi)發(fā)的一種基于 HTTP 的自適應(yīng)比特率流媒體傳輸協(xié)議。這篇文章主要介紹了基于 HLS 創(chuàng)建 Golang 視頻流服務(wù)器,需要的朋友可以參考下2021-08-08
Go語(yǔ)言學(xué)習(xí)之new函數(shù)的用法詳解
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中new()函數(shù)的相關(guān)知識(shí)以及具體用法,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解一下2023-05-05
go程序測(cè)試CPU占用率統(tǒng)計(jì)ps?vs?top兩種不同方式對(duì)比
這篇文章主要為大家介紹了go程序測(cè)試CPU占用率統(tǒng)計(jì)ps?vs?top兩種不同方式對(duì)比,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
Golang仿ps實(shí)現(xiàn)獲取Linux進(jìn)程信息
這篇文章主要為大家學(xué)習(xí)介紹了Golang如何仿ps實(shí)現(xiàn)獲取Linux進(jìn)程信息,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2023-07-07
Golang?IOT中的數(shù)據(jù)序列化與解析過(guò)程
這篇文章主要介紹了Golang?IOT中的數(shù)據(jù)序列化與解析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05
Go實(shí)現(xiàn)socks5服務(wù)器的方法
SOCKS5 是一個(gè)代理協(xié)議,它在使用TCP/IP協(xié)議通訊的前端機(jī)器和服務(wù)器機(jī)器之間扮演一個(gè)中介角色,使得內(nèi)部網(wǎng)中的前端機(jī)器變得能夠訪問(wèn)Internet網(wǎng)中的服務(wù)器,或者使通訊更加安全,這篇文章主要介紹了Go實(shí)現(xiàn)socks5服務(wù)器的方法,需要的朋友可以參考下2023-07-07
Goland IDEA項(xiàng)目多開(kāi)設(shè)置方式
這篇文章主要介紹了Goland IDEA項(xiàng)目多開(kāi)設(shè)置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
重學(xué)Go語(yǔ)言之錯(cuò)誤處理與異常機(jī)制詳解
Go語(yǔ)言的開(kāi)發(fā)者顯然覺(jué)得?try-catch被濫用了,因此?Go不支持使用?try-catch語(yǔ)句捕獲異常處理,那么,Go語(yǔ)言是如何定義和處理程序的異常呢,下面我們就來(lái)看看吧2023-08-08

