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)有定義自己的暫?;蛟偃朦c(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變量從??臻g轉(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條件是否滿(mǎn)足
    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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容