go并發(fā)基礎(chǔ)

中文版Concurrency In Go讀書(shū)筆記:https://www.kancloud.cn/mutouzhang/go/596804

1. sync.Cond + time.Tick

cond := sync.NewCond(&sync.Mutex{})
go func() {
    for range time.Tick(1 * time.Millisecond) {
        cond.Broadcast()   // 每隔1ms喚醒阻塞在該條件變臉上的goroutine
    }
}()

2. 粗粒度鎖 vs 細(xì)粒度鎖(饑餓現(xiàn)象)

package main

import (
    "fmt"
    "sync"
    "time"
)

/*
* 結(jié)論: 粗粒度鎖(3ns)相比細(xì)粒度鎖(1ns)沥阳,更容易搶占cpu資源损搬,容易導(dǎo)致細(xì)粒度鎖的goroutine餓死
 */

func main() {

    var wg sync.WaitGroup
    var sharedLock sync.Mutex
    const runtime = 1 * time.Second

    // 粗粒度鎖goroutine
    greedyWorker := func() {
        defer wg.Done()

        var count int
        for begin := time.Now(); time.Since(begin) <= runtime; {
            sharedLock.Lock()
            time.Sleep(3 * time.Nanosecond)
            sharedLock.Unlock()
            count++
        }

        fmt.Printf("Greedy worker was able to execute %v work loops\n", count)
    }

    // 細(xì)粒度鎖goroutine
    politeWorker := func() {
        defer wg.Done()

        var count int
        for begin := time.Now(); time.Since(begin) <= runtime; {

            sharedLock.Lock()
            time.Sleep(1 * time.Nanosecond)
            sharedLock.Unlock()

            sharedLock.Lock()
            time.Sleep(1 * time.Nanosecond)
            sharedLock.Unlock()

            sharedLock.Lock()
            time.Sleep(1 * time.Nanosecond)
            sharedLock.Unlock()

            count++
        }

        fmt.Printf("Polite worker was able to execute %v work loops.\n", count)
    }

    wg.Add(2)
    go greedyWorker()
    go politeWorker()
    wg.Wait()
}

3. 內(nèi)存同步訪問(wèn):加鎖

package main

import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)

func main() {
    rand.Seed(time.Now().UnixNano())
    var memoryAccess sync.Mutex // <1>
    var value int
    go func() {
        time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond)
        memoryAccess.Lock() // <2>
        value++
        memoryAccess.Unlock() // <3>
    }()

    time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond)
    memoryAccess.Lock() // <4>
    if value == 0 {
        fmt.Printf("the value is %v.\n", value)
    } else {
        fmt.Printf("the value is %v.\n", value)
    }
    memoryAccess.Unlock() // <5>
}

4. go執(zhí)行外部命令

exec.Command(命令名,參數(shù)).Run()   // 例如 ./cmd -deploy=aaa

5. goroutine背后的知識(shí)

goroutine不是操作系統(tǒng)線程喻杈,也不完全是綠色的線程(由語(yǔ)言運(yùn)行時(shí)管理的線程)局装,其是更高層次的抽象碳想,被成為協(xié)程宋渔。

協(xié)程是非搶占的并發(fā)子程序,也就是說(shuō)goroutine不能被中斷。

Go的獨(dú)特之處在于goroutine與Go的runtime深度整合揪垄,goroutine沒(méi)有定義自己的暫颓钏保或再入點(diǎn),Go的runtime會(huì)監(jiān)視goroutine的運(yùn)行時(shí)行為饥努,并在goroutine阻塞時(shí)自動(dòng)掛起它們捡鱼,在goroutine變通暢時(shí)恢復(fù)它們。

Go的宿主機(jī)制實(shí)現(xiàn)了所謂的M:N調(diào)度器(GPM模型)肪凛,這意味著它可以將M個(gè)綠色線程映射到N個(gè)系統(tǒng)線程堰汉,goroutine隨后被安排在這些綠色線程上辽社。

Go并發(fā)遵循fork-join模型伟墙,即fork的子goroutine在任務(wù)結(jié)束時(shí),最終還是會(huì)合并到主goroutine上的滴铅。go關(guān)鍵字為Go程序?qū)崿F(xiàn)了fork戳葵,fork的執(zhí)行者是goroutine。如下圖所示:


frok-join模型

下面的go程序代碼:

    var wg sync.WaitGroup
    for _, salutation := range []string{"hello", "greetings", "good day"} {
        wg.Add(1)
        go func() {
            defer wg.Done()
            fmt.Println(salutation) // 1
        }()
    }
    wg.Wait()  

最終輸出結(jié)果為: good day三次

  • main goroutine不能被中斷汉匙,只有在運(yùn)行到wg.Wait時(shí)被阻塞拱烁,此時(shí)其余goroutine才會(huì)被調(diào)度執(zhí)行
  • go內(nèi)存管理機(jī)制:salutation變量從棧空間轉(zhuǎn)移至堆空間噩翠,其保存的值為"good day"

因此戏自,程序中的子goroutine在被調(diào)度執(zhí)行時(shí),salutation變量的值均為good day伤锚。

Tips
新建立一個(gè)goroutine有幾千字節(jié)擅笔,這樣的大小幾乎總是夠用的。如果出現(xiàn)不夠用的情況屯援,Go的runtime會(huì)自動(dòng)增加(或縮小)用于存儲(chǔ)堆棧的內(nèi)存猛们,從而允許goroutine存在適量?jī)?nèi)存中。因此狞洋,在C/C++等語(yǔ)言中容易發(fā)生的爆椡涮裕現(xiàn)象在Go中并不會(huì)發(fā)生,因?yàn)間oroutine對(duì)應(yīng)的堆椉茫空間是可以動(dòng)態(tài)增長(zhǎng)的庐橙。在相同的地址空間中創(chuàng)建數(shù)十萬(wàn)個(gè)goroutine是可以的,如果這些goroutine只是執(zhí)行等同于線程的任務(wù)借嗽,那么系統(tǒng)資源的占用將會(huì)更小态鳖。

一種GC無(wú)法回收goroutine的情況:goroutine泄露

    go func() {
        // goroutine在此處永久阻塞
    }()
    // do work

一個(gè)計(jì)算goroutine占用內(nèi)存空間大小的程序,通過(guò)運(yùn)行結(jié)果可以看出一個(gè)goroutine是多么的輕量級(jí)淹魄。

package main

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

func main() {

    memConsumed := func() uint64 { // 占用內(nèi)存測(cè)量函數(shù)
        runtime.GC()
        var s runtime.MemStats
        runtime.ReadMemStats(&s)
        return s.Sys
    }

    var c <-chan interface{}
    var wg sync.WaitGroup
    noop := func() { wg.Done(); <-c } // 1 : goroutine將會(huì)一直被阻塞

    const numGoroutines = 1e4 // 2 : 創(chuàng)建1W個(gè)goroutine
    wg.Add(numGoroutines)
    before := memConsumed() // 3 : 測(cè)量創(chuàng)建goroutine前郁惜,內(nèi)存占用大小
    for i := numGoroutines; i > 0; i-- {
        go noop()
    }
    wg.Wait()
    after := memConsumed() // 4 : 測(cè)量創(chuàng)建1Wgoroutine之后,內(nèi)存占用情況
    fmt.Printf("%.3fkb", float64(after-before)/numGoroutines/1000)
} // 測(cè)量結(jié)果: 每個(gè)goroutine占用內(nèi)存空間大小約為2.61KB

測(cè)試goroutine上下文切換的性能

// 單純模擬兩個(gè)goroutine之間的數(shù)據(jù)傳輸,進(jìn)行g(shù)oroutine上下文切換性能的統(tǒng)計(jì)
func BenchmarkContextSwitch(b *testing.B) {

    var wg sync.WaitGroup
    begin := make(chan struct{})
    c := make(chan struct{})

    // 只是單純地模擬兩個(gè)goroutine之間傳送數(shù)據(jù)
    var token struct{}
    sender := func() {
        defer wg.Done()
        <-begin //1: 阻塞
        for i := 0; i < b.N; i++ {
            c <- token //2: 發(fā)送
        }
    }
    receiver := func() {
        defer wg.Done()
        <-begin //1: 阻塞
        for i := 0; i < b.N; i++ {
            <-c //3: 接收
        }
    }

    wg.Add(2)
    go sender()
    go receiver()
    b.StartTimer() //4: 啟動(dòng)定時(shí)器
    close(begin)   //5: 啟動(dòng)兩個(gè)goroutine之間的數(shù)據(jù)傳輸, close channel --> done channel --> 進(jìn)行信號(hào)廣播
    wg.Wait()
}

// 基準(zhǔn)測(cè)試結(jié)果如下:
?  learndemo **go test -bench=. -cpu=1 /Users/didi/MyWork/PersonalCode/src/go_demo/learndemo/context_switch_test.go**
goos: darwin
goarch: amd64
BenchmarkContextSwitch  10000000           **165 ns/op**
PASS
ok      command-line-arguments  1.830s
?  learndemo

6. channel相關(guān)知識(shí)

channel操作注意事項(xiàng)

作為擁有channnel的goroutine(生產(chǎn)者)兆蕉,應(yīng)該確保以下三件事情:

  • 初始化該channel
  • 執(zhí)行寫(xiě)入操作或?qū)⑺袡?quán)交給另一個(gè)goroutine
  • 關(guān)閉該channel

作為channel的消費(fèi)者羽戒,只需要考慮兩件事情:

  • channel什么時(shí)候被關(guān)閉(close)
  • 處理基于任何原因出現(xiàn)的阻塞(block)

一個(gè)簡(jiǎn)單的生產(chǎn)者/消費(fèi)者示例:

chanOwner := func() <-chan int {   // 返回一個(gè)只讀channel

    resultStream := make(chan int, 5)//1
    go func() {//2
        defer close(resultStream)//3: defer close channel
        for i := 0; i <= 5; i++ {
            resultStream <- i  // 生產(chǎn)數(shù)據(jù)
        }
    }()
    return resultStream//4

}
// 生產(chǎn)者創(chuàng)建channel,并向channel中寫(xiě)入數(shù)據(jù)虎韵,生產(chǎn)結(jié)束后關(guān)閉channel(defer close)
resultStream := chanOwner()
for result := range resultStream {//5
    fmt.Printf("Received: %d\n", result)
}  // 消費(fèi)者消費(fèi)數(shù)據(jù)易稠,可能會(huì)阻塞住,且在channel close時(shí)包蓝,執(zhí)行退出操作
fmt.Println("Done receiving!")

7. select

select + time超時(shí)控制

var c <-chan int
select {
case <-c: //1
case <-time.After(1 * time.Second):
    fmt.Println("Timed out.")
}

select+default

start := time.Now()
var c1, c2 <-chan int
select {
case <-c1:
case <-c2:
default:
    fmt.Printf("In default after %v\n\n", time.Since(start))
}

for-select

done := make(chan interface{})
go func() {
    time.Sleep(5 * time.Second)
    close(done)
}()

workCounter := 0
loop:
for {   // 不斷循環(huán)驶社,判斷select-case條件是否滿足
    select {
    case <-done:
        break loop   // break tag使用方法,跳出指定的多層循環(huán)
    default:
    }

    // Simulate work
    workCounter++
    time.Sleep(1 * time.Second)
}

fmt.Printf("Achieved %v cycles of work before signalled to stop.\n", workCounter)

永久阻塞的select語(yǔ)句

select {}   // select沒(méi)有case分支测萎,永久被阻塞

8. GOMAXPROCS

runtime.GOMAXPROCS(runtime.NumCPU())  // 指定G-P-M模型中的P的個(gè)數(shù)亡电,從而決定了其能夠利用的操作系統(tǒng)線程的最大數(shù)目,多核情況下goroutine運(yùn)行的并行程度
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末硅瞧,一起剝皮案震驚了整個(gè)濱河市份乒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌腕唧,老刑警劉巖或辖,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異枣接,居然都是意外死亡颂暇,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)但惶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)耳鸯,“玉大人,你說(shuō)我怎么就攤上這事榆骚∑模” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵妓肢,是天一觀的道長(zhǎng)捌省。 經(jīng)常有香客問(wèn)我,道長(zhǎng)碉钠,這世上最難降的妖魔是什么纲缓? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮喊废,結(jié)果婚禮上祝高,老公的妹妹穿的比我還像新娘。我一直安慰自己污筷,他們只是感情好工闺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般陆蟆。 火紅的嫁衣襯著肌膚如雪雷厂。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天叠殷,我揣著相機(jī)與錄音改鲫,去河邊找鬼。 笑死林束,一個(gè)胖子當(dāng)著我的面吹牛像棘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播壶冒,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼缕题,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了依痊?” 一聲冷哼從身側(cè)響起避除,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胸嘁,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體凉逛,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡性宏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了状飞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毫胜。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖诬辈,靈堂內(nèi)的尸體忽然破棺而出酵使,到底是詐尸還是另有隱情,我是刑警寧澤焙糟,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布口渔,位于F島的核電站,受9級(jí)特大地震影響穿撮,放射性物質(zhì)發(fā)生泄漏缺脉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一悦穿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦舰涌、人聲如沸呀伙。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)太伊。三九已至负蠕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間倦畅,已是汗流浹背遮糖。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留叠赐,地道東北人欲账。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像芭概,于是被迫代替她去往敵國(guó)和親赛不。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

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

  • 除了保證操作的原子性以外罢洲,同步還可以保證變量在不同線程之間的內(nèi)存可見(jiàn)性踢故。原子性和可見(jiàn)性共同構(gòu)成了同步的兩個(gè)核心要素...
    namelessEcho閱讀 327評(píng)論 0 0
  • 第二章 線程管理 數(shù)據(jù)保護(hù) 從樂(lè)觀的角度上看,還是有方法可循的:切勿將受保護(hù)數(shù)據(jù)的指針或引用傳遞到互斥鎖作用域之外...
    scott_yu779閱讀 628評(píng)論 0 0
  • 最近快手這種小視頻app,特別的火惹苗,中午吃過(guò)午飯殿较,閑來(lái)無(wú)聊,想搞下快手的短視頻桩蓉,看能不能搞到淋纲。 于是乎, 打開(kāi)了f...
    小賢tx閱讀 4,569評(píng)論 1 1
  • 相思月 月牙泉畔 星星搖曳 泛著清幽的光 你身穿一襲潔白的長(zhǎng)裙 帶著淺藍(lán)色的微笑 俘獲了我的...
    秋水長(zhǎng)天_42b2閱讀 189評(píng)論 0 2
  • 把一個(gè)像素 放大至 N個(gè)像素去顯示(N就是我們像素比的值) 舉個(gè)例子: 如果像素比為2 那么院究,我們div實(shí)際所占的...
    llpy閱讀 1,200評(píng)論 0 0