Go語言并發(fā)編程之互斥鎖Mutex和讀寫鎖RWMutex
在并發(fā)編程中,多個(gè)Goroutine訪問同一塊內(nèi)存資源時(shí)可能會(huì)出現(xiàn)競(jìng)態(tài)條件,我們需要在臨界區(qū)中使用適當(dāng)?shù)耐讲僮鱽硪员苊飧?jìng)態(tài)條件。Go 語言中提供了很多同步工具,本文將介紹互斥鎖Mutex和讀寫鎖RWMutex的使用方法。
一、互斥鎖Mutex
1、Mutex介紹
Go 語言的同步工具主要由 sync 包提供,互斥鎖 (Mutex) 與讀寫鎖 (RWMutex) 就是sync 包中的方法。
互斥鎖可以用來保護(hù)一個(gè)臨界區(qū),保證同一時(shí)刻只有一個(gè) goroutine 處于該臨界區(qū)內(nèi)。主要包括鎖定(Lock方法)和解鎖(Unlock方法)兩個(gè)操作,首先對(duì)進(jìn)入臨界區(qū)的goroutine進(jìn)行鎖定,離開時(shí)進(jìn)行解鎖。
使用互斥鎖 (Mutex)時(shí)要注意以下幾點(diǎn):
- 不要重復(fù)鎖定互斥鎖,否則會(huì)阻塞,也可能會(huì)導(dǎo)致死鎖(
deadlock); - 要對(duì)互斥鎖進(jìn)行解鎖,這也是為了避免重復(fù)鎖定;
- 不要對(duì)未鎖定或者已解鎖的互斥鎖解鎖;
- 不要在多個(gè)函數(shù)之間直接傳遞互斥鎖,
sync.Mutex類型屬于值類型,將它傳給一個(gè)函數(shù)時(shí),會(huì)產(chǎn)生一個(gè)副本,在函數(shù)中對(duì)鎖的操作不會(huì)影響原鎖
總之,一個(gè)互斥鎖只用來保護(hù)一個(gè)臨界區(qū),加鎖后記得解鎖,對(duì)于每一個(gè)鎖定操作,都要有且只有一個(gè)對(duì)應(yīng)的解鎖操作,也就是加鎖和解鎖要成對(duì)出現(xiàn),最保險(xiǎn)的做法時(shí)使用 defer語句 解鎖。
2、Mutex使用實(shí)例
下面的代碼模擬取錢和存錢操作:
package main
import (
"flag"
"fmt"
"sync"
)
var (
mutex sync.Mutex
balance int
protecting uint // 是否加鎖
sign = make(chan struct{}, 10) //通道,用于等待所有g(shù)oroutine
)
// 存錢
func deposit(value int) {
defer func() {
sign <- struct{}{}
}()
if protecting == 1 {
mutex.Lock()
defer mutex.Unlock()
}
fmt.Printf("余額: %d\n", balance)
balance += value
fmt.Printf("存 %d 后的余額: %d\n", value, balance)
fmt.Println()
}
// 取錢
func withdraw(value int) {
defer func() {
sign <- struct{}{}
}()
if protecting == 1 {
mutex.Lock()
defer mutex.Unlock()
}
fmt.Printf("余額: %d\n", balance)
balance -= value
fmt.Printf("取 %d 后的余額: %d\n", value, balance)
fmt.Println()
}
func main() {
for i:=0; i < 5; i++ {
go withdraw(500) // 取500
go deposit(500) // 存500
}
for i := 0; i < 10; i++ {
<-sign
}
fmt.Printf("當(dāng)前余額: %d\n", balance)
}
func init() {
balance = 1000 // 初始賬戶余額為1000
flag.UintVar(&protecting, "protecting", 0, "是否加鎖,0表示不加鎖,1表示加鎖")
}
上面的代碼中,使用了通道來讓主 goroutine 等待其他 goroutine 運(yùn)行結(jié)束,每個(gè)子goroutine在運(yùn)行結(jié)束之前向通道發(fā)送一個(gè)元素,主 goroutine 在最后從這個(gè)通道接收元素,接收次數(shù)與子goroutine個(gè)數(shù)相同。接收完后就會(huì)退出主goroutine。
代碼使用協(xié)程實(shí)現(xiàn)多次(5次)對(duì)一個(gè)賬戶進(jìn)行存錢和取錢的操作,先來看不加鎖的情況:
余額: 1000 存 500 后的余額: 1500 余額: 1000 取 500 后的余額: 1000 余額: 1000 存 500 后的余額: 1500 余額: 1000 取 500 后的余額: 1000 余額: 1000 存 500 后的余額: 1500 余額: 1000 取 500 后的余額: 1000 余額: 1000 取 500 后的余額: 500 余額: 1000 存 500 后的余額: 1000 余額: 1000 取 500 后的余額: 500 余額: 1000 存 500 后的余額: 1000 當(dāng)前余額: 1000
可以看到出現(xiàn)了混亂,比如第二次1000的余額取500后還是1000,這種對(duì)同一資源的競(jìng)爭(zhēng)出現(xiàn)了競(jìng)態(tài)條件(Race Condition)。
下面來看加鎖的執(zhí)行結(jié)果:
余額: 1000 取 500 后的余額: 500 余額: 500 存 500 后的余額: 1000 余額: 1000 取 500 后的余額: 500 余額: 500 存 500 后的余額: 1000 余額: 1000 取 500 后的余額: 500 余額: 500 存 500 后的余額: 1000 余額: 1000 存 500 后的余額: 1500 余額: 1500 取 500 后的余額: 1000 余額: 1000 取 500 后的余額: 500 余額: 500 存 500 后的余額: 1000 當(dāng)前余額: 1000
加鎖后就正常了。
下面介紹更細(xì)化的互斥鎖:讀/寫互斥鎖RWMutex。
二、讀寫鎖RWMutex
1、RWMutex介紹
讀/寫互斥鎖RWMutex包含了讀鎖和寫鎖,分別對(duì)共享資源的“讀操作”和“寫操作”進(jìn)行保護(hù)。sync.RWMutex類型中的Lock方法和Unlock方法分別用于對(duì)寫鎖進(jìn)行鎖定和解鎖,而它的RLock方法和RUnlock方法則分別用于對(duì)讀鎖進(jìn)行鎖定和解鎖。
有了互斥鎖Mutex,為什么還需要讀寫鎖呢?因?yàn)樵诤芏嗖l(fā)操作中,并發(fā)讀取占比很大,寫操作相對(duì)較少,讀寫鎖可以并發(fā)讀取,這樣可以提供服務(wù)性能。讀寫鎖具有以下特征:
| 讀寫鎖 | 讀鎖 | 寫鎖 |
|---|---|---|
| 讀鎖 | Yes | No |
| 寫鎖 | No | No |
也就是說,
- 如果某個(gè)共享資源受到讀鎖和寫鎖保護(hù)時(shí),其它
goroutine不能進(jìn)行寫操作。換句話說就是讀寫操作和寫寫操作不能并行執(zhí)行,也就是讀寫互斥; - 受讀鎖保護(hù)時(shí),可以同時(shí)進(jìn)行多個(gè)讀操作。
在使用讀寫鎖時(shí),還需要注意:
- 不要對(duì)未鎖定的讀寫鎖解鎖;
- 對(duì)讀鎖不能使用寫鎖解鎖
- 對(duì)寫鎖不能使用讀鎖解鎖
2、RWMutex使用實(shí)例
改寫前面的取錢和存錢操作,添加查詢余額的方法:
package main
import (
"fmt"
"sync"
)
// account 代表計(jì)數(shù)器。
type account struct {
num uint // 操作次數(shù)
balance int // 余額
rwMu *sync.RWMutex // 讀寫鎖
}
var sign = make(chan struct{}, 15) //通道,用于等待所有g(shù)oroutine
// 查看余額:使用讀鎖
func (c *account) check() {
defer func() {
sign <- struct{}{}
}()
c.rwMu.RLock()
defer c.rwMu.RUnlock()
fmt.Printf("%d 次操作后的余額: %d\n", c.num, c.balance)
}
// 存錢:寫鎖
func (c *account) deposit(value int) {
defer func() {
sign <- struct{}{}
}()
c.rwMu.Lock()
defer c.rwMu.Unlock()
fmt.Printf("余額: %d\n", c.balance)
c.num += 1
c.balance += value
fmt.Printf("存 %d 后的余額: %d\n", value, c.balance)
fmt.Println()
}
// 取錢:寫鎖
func (c *account) withdraw(value int) {
defer func() {
sign <- struct{}{}
}()
c.rwMu.Lock()
defer c.rwMu.Unlock()
fmt.Printf("余額: %d\n", c.balance)
c.num += 1
c.balance -= value
fmt.Printf("取 %d 后的余額: %d\n", value, c.balance)
fmt.Println()
}
func main() {
c := account{0, 1000, new(sync.RWMutex)}
for i:=0; i < 5; i++ {
go c.withdraw(500) // 取500
go c.deposit(500) // 存500
go c.check()
}
for i := 0; i < 15; i++ {
<-sign
}
fmt.Printf("%d 次操作后的余額: %d\n", c.num, c.balance)
}
執(zhí)行結(jié)果:
余額: 1000 取 500 后的余額: 500 1 次操作后的余額: 500 1 次操作后的余額: 500 1 次操作后的余額: 500 1 次操作后的余額: 500 1 次操作后的余額: 500 余額: 500 存 500 后的余額: 1000 余額: 1000 取 500 后的余額: 500 余額: 500 存 500 后的余額: 1000 余額: 1000 存 500 后的余額: 1500 余額: 1500 取 500 后的余額: 1000 余額: 1000 取 500 后的余額: 500 余額: 500 存 500 后的余額: 1000 余額: 1000 取 500 后的余額: 500 余額: 500 存 500 后的余額: 1000 10 次操作后的余額: 1000
讀寫鎖和互斥鎖的不同之處在于讀寫鎖把對(duì)共享資源的讀操作和寫操作分開了,可以實(shí)現(xiàn)更復(fù)雜的訪問控制。
總結(jié):
讀寫鎖也是一種互斥鎖,它是互斥鎖的擴(kuò)展。在使用時(shí)需要注意:
- 加鎖后一定要解鎖
- 不要重復(fù)加鎖或者解鎖
- 不解鎖未鎖定的鎖
- 不要傳遞互斥鎖
到此這篇關(guān)于Go語言并發(fā)編程之互斥鎖Mutex和讀寫鎖RWMutex的文章就介紹到這了,更多相關(guān)Go語言 Mutex RWMutex內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
探索分析Go?HTTP?GET請(qǐng)求發(fā)送body
這篇文章主要為大家介紹了探索分析Go?HTTP?GET請(qǐng)求發(fā)送body,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
GO語言創(chuàng)建錢包并遍歷錢包(wallet)的實(shí)現(xiàn)代碼
比特幣錢包實(shí)際上是一個(gè)密鑰對(duì),當(dāng)你安裝 一個(gè)錢包應(yīng)用,或者是使用一個(gè)比特幣客戶端來生成一個(gè)新地址是,他就會(huì)為你生成一個(gè)密鑰對(duì),今天通過本文給大家分享go語言遍歷錢包的相關(guān)知識(shí),一起看看吧2021-05-05
Go緩沖channel和非緩沖channel的區(qū)別說明
這篇文章主要介紹了Go緩沖channel和非緩沖channel的區(qū)別說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-04-04
golang組件swagger生成接口文檔實(shí)踐示例
這篇文章主要為大家介紹了golang組件swagger生成接口文檔實(shí)踐示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04
Go通過SJSON實(shí)現(xiàn)動(dòng)態(tài)修改JSON
在Go語言 json 處理領(lǐng)域,在 json 數(shù)據(jù)處理中,讀取與修改是兩個(gè)核心需求,本文我們就來看看如何使用SJSON進(jìn)行動(dòng)態(tài)修改JSON吧,有需要的小伙伴可以了解下2025-03-03
Golang map實(shí)踐及實(shí)現(xiàn)原理解析
這篇文章主要介紹了Golang map實(shí)踐以及實(shí)現(xiàn)原理,Go 語言中,通過哈希查找表實(shí)現(xiàn) map,用鏈表法解決哈希沖突,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-06-06
golang 實(shí)現(xiàn)時(shí)間滑動(dòng)窗口的示例代碼
滑動(dòng)時(shí)間窗口就是把一段時(shí)間片分為多個(gè)樣本窗口,可以通過更細(xì)粒度對(duì)數(shù)據(jù)進(jìn)行統(tǒng)計(jì),這篇文章主要介紹了golang 實(shí)現(xiàn)時(shí)間滑動(dòng)窗口,需要的朋友可以參考下2022-10-10
深入理解Go Gin框架中間件的實(shí)現(xiàn)原理
在Go Gin框架中,中間件是一種在請(qǐng)求處理過程中插入的功能模塊,它可以用于處理請(qǐng)求的前置和后置邏輯,例如認(rèn)證、日志記錄、錯(cuò)誤處理等,本文將給大家介紹一下Go Gin框架中間件的實(shí)現(xiàn)原理,需要的朋友可以參考下2023-09-09

