您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關Go語言中什么是互斥鎖,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
Go
語言的同步工具主要由 sync
包提供,互斥鎖 (Mutex
) 與讀寫鎖 (RWMutex
) 就是sync
包中的方法。
互斥鎖可以用來保護一個臨界區,保證同一時刻只有一個 goroutine
處于該臨界區內。主要包括鎖定(Lock
方法)和解鎖(Unlock
方法)兩個操作,首先對進入臨界區的goroutine
進行鎖定,離開時進行解鎖。
使用互斥鎖 (Mutex)時要注意以下幾點:
不要重復鎖定互斥鎖,否則會阻塞,也可能會導致死鎖(deadlock
);
要對互斥鎖進行解鎖,這也是為了避免重復鎖定;
不要對未鎖定或者已解鎖的互斥鎖解鎖;
不要在多個函數之間直接傳遞互斥鎖,sync.Mutex
類型屬于值類型,將它傳給一個函數時,會產生一個副本,在函數中對鎖的操作不會影響原鎖
總之,一個互斥鎖只用來保護一個臨界區,加鎖后記得解鎖,對于每一個鎖定操作,都要有且只有一個對應的解鎖操作,也就是加鎖和解鎖要成對出現,最保險的做法時使用defer
語句解鎖。
下面的代碼模擬取錢和存錢操作:
package main import ( "flag" "fmt" "sync" ) var ( mutex sync.Mutex balance int protecting uint // 是否加鎖 sign = make(chan struct{}, 10) //通道,用于等待所有goroutine ) // 存錢 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\n", balance) } func init() { balance = 1000 // 初始賬戶余額為1000 flag.UintVar(&protecting, "protecting", 0, "是否加鎖,0表示不加鎖,1表示加鎖") }
上面的代碼中,使用了通道來讓主 goroutine 等待其他 goroutine 運行結束,每個子goroutine在運行結束之前向通道發送一個元素,主
goroutine
在最后從這個通道接收元素,接收次數與子goroutine個數相同。接收完后就會退出主goroutine。
代碼使用協程實現多次(5次)對一個賬戶進行存錢和取錢的操作,先來看不加鎖的情況
余額: 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
當前余額: 1000
可以看到出現了混亂,比如第二次1000的余額取500后還是1000,這種對同一資源的競爭出現了競態條件(Race Condition)。
下面來看加鎖的執行結果:
余額: 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當前余額: 1000
加鎖后就正常了。
下面介紹更細化的互斥鎖:讀/寫互斥鎖RWMutex
。
讀/寫互斥鎖RWMutex
包含了讀鎖和寫鎖,分別對共享資源的“讀操作”和“寫操作”進行保護。sync.RWMutex
類型中的Lock
方法和Unlock
方法分別用于對寫鎖進行鎖定和解鎖,而它的RLock
方法和RUnlock
方法則分別用于對讀鎖進行鎖定和解鎖。
有了互斥鎖Mutex
,為什么還需要讀寫鎖呢?因為在很多并發操作中,并發讀取占比很大,寫操作相對較少,讀寫鎖可以并發讀取,這樣可以提供服務性能。讀寫鎖具有以下特征:
讀寫鎖 | 讀鎖 | 寫鎖 |
---|---|---|
讀鎖 | Yes | No |
寫鎖 | No | No |
也就是說:
如果某個共享資源受到讀鎖和寫鎖保護時,其它goroutine
不能進行寫操作。換句話說就是讀寫操作和寫寫操作不能并行執行,也就是讀寫互斥;
受讀鎖保護時,可以同時進行多個讀操作。
在使用讀寫鎖時,還需要注意:
不要對未鎖定的讀寫鎖解鎖;
對讀鎖不能使用寫鎖解鎖
對寫鎖不能使用讀鎖解鎖
改寫前面的取錢和存錢操作,添加查詢余額的方法:
package main import ( "fmt" "sync" ) // account 代表計數器。 type account struct { num uint // 操作次數 balance int // 余額 rwMu *sync.RWMutex // 讀寫鎖 } var sign = make(chan struct{}, 15) //通道,用于等待所有goroutine // 查看余額:使用讀鎖 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) }
執行結果:
余額: 1000
取 500 后的余額: 5001 次操作后的余額: 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 后的余額: 100010 次操作后的余額: 1000
讀寫鎖和互斥鎖的不同之處在于讀寫鎖把對共享資源的讀操作和寫操作分開了,可以實現更復雜的訪問控制。
讀寫鎖也是一種互斥鎖,它是互斥鎖的擴展。在使用時需要注意:
加鎖后一定要解鎖
不要重復加鎖或者解鎖
不解鎖未鎖定的鎖
不要傳遞互斥鎖
關于Go語言中什么是互斥鎖就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。