Go語言實(shí)戰(zhàn)筆記(十三)| Go 并發(fā)資源競爭

《Go語言實(shí)戰(zhàn)》讀書筆記爹橱,未完待續(xù)萨螺,歡迎掃碼關(guān)注公眾號flysnow_org,第一時(shí)間看后續(xù)筆記愧驱。覺得有幫助的話慰技,順手分享到朋友圈吧,感謝支持组砚。

有并發(fā)吻商,就有資源競爭,如果兩個(gè)或者多個(gè)goroutine在沒有相互同步的情況下糟红,訪問某個(gè)共享的資源艾帐,比如同時(shí)對該資源進(jìn)行讀寫時(shí),就會(huì)處于相互競爭的狀態(tài)盆偿,這就是并發(fā)中的資源競爭柒爸。

并發(fā)本身并不復(fù)雜,但是因?yàn)橛辛速Y源競爭的問題陈肛,就使得我們開發(fā)出好的并發(fā)程序變得復(fù)雜起來,因?yàn)闀?huì)引起很多莫名其妙的問題兄裂。

package main

import (
    "fmt"
    "runtime"
    "sync"
)

var (
    count int32
    wg    sync.WaitGroup
)

func main() {
    wg.Add(2)
    go incCount()
    go incCount()
    wg.Wait()
    fmt.Println(count)
}

func incCount() {
    defer wg.Done()
    for i := 0; i < 2; i++ {
        value := count
        runtime.Gosched()
        value++
        count = value
    }
}

這是一個(gè)資源競爭的例子句旱,我們可以多運(yùn)行幾次這個(gè)程序阳藻,會(huì)發(fā)現(xiàn)結(jié)果可能是2,也可以是3谈撒,也可能是4腥泥。因?yàn)楣蚕碣Y源count變量沒有任何同步保護(hù),所以兩個(gè)goroutine都會(huì)對其進(jìn)行讀寫啃匿,會(huì)導(dǎo)致對已經(jīng)計(jì)算好的結(jié)果覆蓋妈踊,以至于產(chǎn)生錯(cuò)誤結(jié)果猜扮,這里我們演示一種可能,兩個(gè)goroutine我們暫時(shí)稱之為g1和g2。

  1. g1讀取到count為0伶氢。
  2. 然后g1暫停了,切換到g2運(yùn)行浪蹂,g2讀取到count也為0萎庭。
  3. g2暫停,切換到g1光稼,g1對count+1或南,count變?yōu)?。
  4. g1暫停艾君,切換到g2采够,g2剛剛已經(jīng)獲取到值0,對其+1冰垄,最后賦值給count還是1
  5. 有沒有注意到蹬癌,剛剛g1對count+1的結(jié)果被g2給覆蓋了,兩個(gè)goroutine都+1還是1

不再繼續(xù)演示下去了播演,到這里結(jié)果已經(jīng)錯(cuò)了冀瓦,兩個(gè)goroutine相互覆蓋結(jié)果。我們這里的runtime.Gosched()是讓當(dāng)前goroutine暫停的意思写烤,退回執(zhí)行隊(duì)列翼闽,讓其他等待的goroutine運(yùn)行,目的是讓我們演示資源競爭的結(jié)果更明顯洲炊。注意感局,這里還會(huì)牽涉到CPU問題,多核會(huì)并行暂衡,那么資源競爭的效果更明顯询微。

所以我們對于同一個(gè)資源的讀寫必須是原子化的,也就是說狂巢,同一時(shí)間只能有一個(gè)goroutine對共享資源進(jìn)行讀寫操作撑毛。

共享資源競爭的問題,非常復(fù)雜唧领,并且難以察覺藻雌,好在Go為我們提供了一個(gè)工具幫助我們檢查雌续,這個(gè)就是go build -race命令。我們在當(dāng)前項(xiàng)目目錄下執(zhí)行這個(gè)命令胯杭,生成一個(gè)可以執(zhí)行文件驯杜,然后再運(yùn)行這個(gè)可執(zhí)行文件,就可以看到打印出的檢測信息做个。

go build -race

多加了一個(gè)-race標(biāo)志鸽心,這樣生成的可執(zhí)行程序就自帶了檢測資源競爭的功能,下面我們運(yùn)行居暖,也是在終端運(yùn)行顽频。

./hello

我這里示例生成的可執(zhí)行文件名是hello,所以是這么運(yùn)行的膝但,這時(shí)候冲九,我們看終端輸出的檢測結(jié)果。

?  hello ./hello       
==================
WARNING: DATA RACE
Read at 0x0000011a5118 by goroutine 7:
  main.incCount()
      /Users/xxx/code/go/src/flysnow.org/hello/main.go:25 +0x76

Previous write at 0x0000011a5118 by goroutine 6:
  main.incCount()
      /Users/xxx/code/go/src/flysnow.org/hello/main.go:28 +0x9a

Goroutine 7 (running) created at:
  main.main()
      /Users/xxx/code/go/src/flysnow.org/hello/main.go:17 +0x77

Goroutine 6 (finished) created at:
  main.main()
      /Users/xxx/code/go/src/flysnow.org/hello/main.go:16 +0x5f
==================
4
Found 1 data race(s)

看跟束,找到一個(gè)資源競爭莺奸,連在那一行代碼出了問題,都標(biāo)示出來了冀宴。goroutine 7在代碼25行讀取共享資源value := count,而這時(shí)goroutine 6正在代碼28行修改共享資源count = value,而這兩個(gè)goroutine都是從main函數(shù)啟動(dòng)的灭贷,在16、17行略贮,通過go關(guān)鍵字甚疟。

既然我們已經(jīng)知道共享資源競爭的問題,是因?yàn)橥瑫r(shí)有兩個(gè)或者多個(gè)goroutine對其進(jìn)行了讀寫逃延,那么我們只要保證览妖,同時(shí)只有一個(gè)goroutine讀寫不就可以了,現(xiàn)在我們就看下傳統(tǒng)解決資源競爭的辦法--對資源加鎖揽祥。

Go語言提供了atomic包和sync包里的一些函數(shù)對共享資源同步枷鎖讽膏,我們先看下atomic包。

package main

import (
    "fmt"
    "runtime"
    "sync"
    "sync/atomic"
)

var (
    count int32
    wg    sync.WaitGroup
)

func main() {
    wg.Add(2)
    go incCount()
    go incCount()
    wg.Wait()
    fmt.Println(count)
}

func incCount() {
    defer wg.Done()
    for i := 0; i < 2; i++ {
        value := atomic.LoadInt32(&count)
        runtime.Gosched()
        value++
        atomic.StoreInt32(&count,value)
    }
}

留意這里atomic.LoadInt32atomic.StoreInt32兩個(gè)函數(shù)拄丰,一個(gè)讀取int32類型變量的值府树,一個(gè)是修改int32類型變量的值,這兩個(gè)都是原子性的操作料按,Go已經(jīng)幫助我們在底層使用加鎖機(jī)制奄侠,保證了共享資源的同步和安全,所以我們可以得到正確的結(jié)果载矿,這時(shí)候我們再使用資源競爭檢測工具go build -race檢查垄潮,也不會(huì)提示有問題了。

atomic包里還有很多原子化的函數(shù)可以保證并發(fā)下資源同步訪問修改的問題,比如函數(shù)atomic.AddInt32可以直接對一個(gè)int32類型的變量進(jìn)行修改弯洗,在原值的基礎(chǔ)上再增加多少的功能甫题,也是原子性的,這里不再舉例涂召,大家自己可以試試。

atomic雖然可以解決資源競爭問題敏沉,但是比較都是比較簡單的果正,支持的數(shù)據(jù)類型也有限,所以Go語言還提供了一個(gè)sync包盟迟,這個(gè)sync包里提供了一種互斥型的鎖秋泳,可以讓我們自己靈活的控制哪些代碼,同時(shí)只能有一個(gè)goroutine訪問攒菠,被sync互斥鎖控制的這段代碼范圍迫皱,被稱之為臨界區(qū),臨界區(qū)的代碼辖众,同一時(shí)間卓起,只能又一個(gè)goroutine訪問。剛剛那個(gè)例子凹炸,我們還可以這么改造戏阅。

package main

import (
    "fmt"
    "runtime"
    "sync"
)

var (
    count int32
    wg    sync.WaitGroup
    mutex sync.Mutex
)

func main() {
    wg.Add(2)
    go incCount()
    go incCount()
    wg.Wait()
    fmt.Println(count)
}

func incCount() {
    defer wg.Done()
    for i := 0; i < 2; i++ {
        mutex.Lock()
        value := count
        runtime.Gosched()
        value++
        count = value
        mutex.Unlock()
    }
}

實(shí)例中,新聲明了一個(gè)互斥鎖mutex sync.Mutex啤它,這個(gè)互斥鎖有兩個(gè)方法奕筐,一個(gè)是mutex.Lock(),一個(gè)是mutex.Unlock(),這兩個(gè)之間的區(qū)域就是臨界區(qū),臨界區(qū)的代碼是安全的变骡。

示例中我們先調(diào)用mutex.Lock()對有競爭資源的代碼加鎖离赫,這樣當(dāng)一個(gè)goroutine進(jìn)入這個(gè)區(qū)域的時(shí)候,其他goroutine就進(jìn)不來了塌碌,只能等待渊胸,一直到調(diào)用mutex.Unlock() 釋放這個(gè)鎖為止。

這種方式比較靈活誊爹,可以讓代碼編寫者任意定義需要保護(hù)的代碼范圍蹬刷,也就是臨界區(qū)。除了原子函數(shù)和互斥鎖频丘,Go還為我們提供了更容易在多個(gè)goroutine同步的功能办成,這就是通道chan,我們下篇講搂漠。

《Go語言實(shí)戰(zhàn)》讀書筆記迂卢,未完待續(xù),歡迎掃碼關(guān)注公眾號flysnow_org,第一時(shí)間看后續(xù)筆記而克。覺得有幫助的話靶壮,順手分享到朋友圈吧,感謝支持员萍。

掃碼關(guān)注
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末腾降,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碎绎,更是在濱河造成了極大的恐慌螃壤,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件筋帖,死亡現(xiàn)場離奇詭異奸晴,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)日麸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門寄啼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人代箭,你說我怎么就攤上這事墩划。” “怎么了嗡综?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵走诞,是天一觀的道長。 經(jīng)常有香客問我蛤高,道長蚣旱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任戴陡,我火速辦了婚禮塞绿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘恤批。我一直安慰自己异吻,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布喜庞。 她就那樣靜靜地躺著诀浪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪延都。 梳的紋絲不亂的頭發(fā)上雷猪,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機(jī)與錄音晰房,去河邊找鬼求摇。 笑死射沟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的与境。 我是一名探鬼主播验夯,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼摔刁!你這毒婦竟也來了挥转?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤共屈,失蹤者是張志新(化名)和其女友劉穎扁位,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趁俊,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年刑然,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寺擂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡泼掠,死狀恐怖怔软,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情择镇,我是刑警寧澤挡逼,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站腻豌,受9級特大地震影響家坎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吝梅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一虱疏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧苏携,春花似錦做瞪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至纱扭,卻和暖如春牍帚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背乳蛾。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工履羞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留峦萎,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓忆首,卻偏偏與公主長得像爱榔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子糙及,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容