<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在學習作業系統的時候,我們應該都學習過臨界區、互斥鎖這些概念,用於在並行環境下保證狀態的正確性。比如在秒殺時,100 個使用者同時搶 10 個電腦,為了避免少賣或者超賣,就需要使用鎖來進行並行控制。在 Go語言 裡面互斥鎖是 sync.Mutex
,我們本篇文章就來學習下為什麼要使用互斥鎖、如何使用互斥鎖,以及使用時的常見問題。
我們來看一個範例:我們起了 10000
個協程將變數 num
加1,因此肯定會存在並行,如果我們不控制並行,10000 個協程都執行完後,該變數的值很大概率不等於 10000。
那麼為什麼會出現這個問題呢,原因是 num++
不是原子操作,它會先讀取變數 num
當前值,然後對這個值 加1
,再把結果儲存到 num
中。例如 10
個 goroutine
同時執行到 num++
這一行,可能同時讀取 num=1000
,都加1
後再儲存, num=1001
,這就與想要的結果不符。
package main import ( "fmt" "sync" ) func main() { num := 0 var wg sync.WaitGroup threadCount := 10000 wg.Add(threadCount) for i := 0; i < threadCount; i++ { go func() { defer wg.Done() num++ }() } wg.Wait() // 等待 10000 個協程都執行完 fmt.Println(num) // 9388(每次都可能不一樣) }
我們如果使用了互斥鎖,可以保證每次進入臨界區的只有一個 goroutine
,一個 goroutine
執行完後,另一個 goroutine
才能進入臨界區執行,最終就實現了並行控制。
並行獲取鎖示意圖
package main import ( "fmt" "sync" ) func main() { num := 0 var mutex sync.Mutex // 互斥鎖 var wg sync.WaitGroup threadCount := 10000 wg.Add(threadCount) for i := 0; i < threadCount; i++ { go func() { defer wg.Done() mutex.Lock() // 加鎖 num++ // 臨界區 mutex.Unlock() // 解鎖 }() } wg.Wait() fmt.Println(num) // 10000 }
Mutex
保持 Go
一貫的簡潔風格,開箱即用,宣告一個變數預設是沒有加鎖的,加鎖使用 Lock()
方法,解鎖使用 Unlock()
方法。
這個在上例中已經體現了,直接看上面的例子就好
我們可以將 Mutex
封裝在 struct
中,封裝成執行緒安全的函數供外部呼叫。比如我們封裝了一個執行緒安全的計數器,呼叫 Add()
就加一,呼叫Count()
返回計數器的值。
package main import ( "fmt" "sync" ) type Counter struct { num int mutex sync.Mutex } // 加一操作,涉及到臨界區 num,加鎖解鎖 func (counter *Counter) Add() { counter.mutex.Lock() defer counter.mutex.Unlock() counter.num++ } // 返回數量,涉及到臨界區 num,加鎖解鎖 func (counter *Counter) Count() int { counter.mutex.Lock() defer counter.mutex.Unlock() return counter.num } func main() { threadCount := 10000 var counter Counter var wg sync.WaitGroup wg.Add(threadCount) for i := 0; i < threadCount; i++ { go func() { defer wg.Done() counter.Add() }() } wg.Wait() // 等待所有 goroutine 都執行完 fmt.Println(counter.Count()) // 10000 }
在 Go
中,map
結構是不支援並行的,如果並行讀寫就會 panic
// 執行會 panic,提示 fatal error: concurrent map writes func main() { m := make(map[string]string) var wait sync.WaitGroup wait.Add(1000) for i := 0; i < 1000; i++ { item := fmt.Sprintf("%d", i) go func() { wait.Done() m[item] = item }() } wait.Wait() }
基於 Mutex
,我們可以實現一個執行緒安全的 map
:
import ( "fmt" "sync" ) type ConcurrentMap struct { mutex sync.Mutex items map[string]interface{} } func (c *ConcurrentMap) Add(key string, value interface{}) { c.mutex.Lock() defer c.mutex.Unlock() c.items[key] = value } func (c *ConcurrentMap) Remove(key string) { c.mutex.Lock() defer c.mutex.Unlock() delete(c.items, key) } func (c *ConcurrentMap) Get(key string) interface{} { c.mutex.Lock() defer c.mutex.Unlock() return c.items[key] } func NewConcurrentMap() ConcurrentMap { return ConcurrentMap{ items: make(map[string]interface{}), } } func main() { m := NewConcurrentMap() var wait sync.WaitGroup wait.Add(1000) for i := 0; i < 1000; i++ { item := fmt.Sprintf("%d", i) go func() { wait.Done() m.Add(item, item) }() } wait.Wait() fmt.Println(m.Get("100")) // 100 }
當然,基於互斥鎖 Mutex
實現的執行緒安全 map
並不是效能最好的,基於讀寫鎖 sync.RWMutex
和 分片 可以實現效能更好的、執行緒安全的 map
,開發中比較常用的並行安全 map
是 orcaman / concurrent-map(https://github.com/orcaman/concurrent-map)。
從上面可以看出,Mutex
的使用過程方法比較簡單,但還是有幾點需要注意:
1.Mutex
是可以在 goroutine A
中加鎖,在 goroutine B
中解鎖的,但是在實際使用中,儘量保證在同一個 goroutine 中加解鎖。比如 goroutine A 申請到了鎖,在處理臨界區資源的時候,goroutine B 把鎖釋放了,但是 A 以為自己還持有鎖,會繼續處理臨界區資源,就可能會出現問題。
2.Mutex
的加鎖解鎖基本都是成對出現,為了解決忘記解鎖,可以使用 defer
語句,在加鎖後直接 defer mutex.Unlock()
;但是如果處理完臨界區資源後還有很多耗時操作,為了儘早釋放鎖,不建議使用 defer
,而是在處理完臨界區資源後就呼叫 mutex.Unlock()
儘早釋放鎖。
// 邏輯複雜,可能會忘記釋放鎖 func main() { var mutex sync.Mutex mutex.Lock() if *** { if *** { // 處理臨界區資源 mutex.Unlock() return } // 處理臨界區資源 mutex.Unlock() return } // 處理臨界區資源 mutex.Unlock() return } // 避免邏輯複雜忘記釋放鎖,使用 defer語句,成對出現 func main() { var mutex sync.Mutex mutex.Lock() defer mutex.Unlock() if *** { if *** { // 處理臨界區資源 return } // 處理臨界區資源 return } // 處理臨界區資源 return }
3.Mutex 不能複製使用
Mutex
是有狀態的,比如我們對一個 Mutex
加鎖後,再進行復制操作,會把當前的加鎖狀態也給複製過去,基於加鎖的 Mutex
再加鎖肯定不會成功。進行復制操作可能聽起來是一個比較低階的錯誤,但是無意間可能就會犯這種錯誤。
package main import ( "fmt" "sync" ) type Counter struct { mutex sync.Mutex num int } func SomeFunc(c Counter) { c.mutex.Lock() defer c.mutex.Unlock() c.num-- } func main() { var counter Counter counter.mutex.Lock() defer counter.mutex.Unlock() counter.num++ // Go都是值傳遞,這裡複製了 counter,此時 counter.mutex 是加鎖狀態,在 SomeFunc 無法再次加鎖,就會一直等待 SomeFunc(counter) }
以上就是初識Golang Mutex互斥鎖的使用的詳細內容,更多關於Golang Mutex互斥鎖的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45