Go 語(yǔ)言學(xué)習(xí)筆記-Goroutine和channel

Goroutine

什么是 Goroutine

Goroutine 是 Go 并行設(shè)計(jì)的核心。Goroutine 說(shuō)到底其實(shí)就是協(xié)程,它比線程更小,十幾個(gè) Goroutine 可能體現(xiàn)在地城就是五六個(gè)線程,Go 語(yǔ)言內(nèi)部實(shí)現(xiàn)了這些 Goroutine 之間的內(nèi)存共享。

執(zhí)行 Goroutine 只需極少的棧內(nèi)存(大概是 4~5KB),會(huì)根據(jù)相應(yīng)的數(shù)據(jù)伸縮。也正因?yàn)槿绱?,可同時(shí)運(yùn)行成千上萬(wàn)個(gè)并發(fā)任務(wù)。Goroutine 比 thread 更易用、更高效、更輕便。

Goroutine 的創(chuàng)建

只需要在函數(shù)調(diào)用語(yǔ)句前添加 go 關(guān)鍵字,就可創(chuàng)建并發(fā)執(zhí)行單元。開發(fā)人員無(wú)需了解任何執(zhí)行細(xì)節(jié),調(diào)度器會(huì)自動(dòng)將其安排到合適的系統(tǒng)線程上執(zhí)行。

Goroutine 的特征

主 go 程結(jié)束,子 go 程隨之退出。

示例

package main

import (
    "fmt"
    "time"
)

func sing() {
    for i := 0; i < 50; i++ {
        fmt.Println("----我正在唱歌----")
        time.Sleep(100 * time.Millisecond)
    }
}

func dance() {
    for i := 0; i < 50; i++ {
        fmt.Println("----我正在跳舞----")
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
  // 子 go 程
    go sing()
    go dance()

  // 主 go程循環(huán)打印
    for {
        ;
    }
}

程序運(yùn)行結(jié)果:

go程示例.png

runtime 包

  • Gosched

    runtime.Gosched() 用于讓出 CPU 時(shí)間片,讓出當(dāng)前 goroutine 的執(zhí)行權(quán)限,調(diào)度器安排其他等待的任務(wù)運(yùn)行,并在下次再獲得 CPU 時(shí)間輪片的時(shí)候,從該處讓 CPU 的位置恢復(fù)執(zhí)行。 ---時(shí)間片輪傳調(diào)度算法

  • Goexit

    調(diào)用 runtime.Goexit() 將立即終止當(dāng)前 goroutine 執(zhí)行,調(diào)度器確保所有已注冊(cè) defer 延遲調(diào)用被執(zhí)行。

    • return:返回當(dāng)前函數(shù)調(diào)用到調(diào)用者那里去。return 之前的 defer 注冊(cè)生效。
    • Goexit():結(jié)束調(diào)用該函數(shù)的當(dāng)前 go 程。Goexit() 之前注冊(cè)的 defer 都生效。
  • GOAMAXPROCS

    調(diào)用 runtime.GOMAXPROCS(),用來(lái)設(shè)置可以并行計(jì)算的 CPU 核數(shù)的最大值,返回返回之前的值。首次調(diào)用返回默認(rèn)值。

  • 補(bǔ)充

    • 每當(dāng)有一個(gè)進(jìn)程啟動(dòng)時(shí),系統(tǒng)會(huì)自動(dòng)打開三個(gè)文件:標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯(cuò)誤。 ---對(duì)應(yīng)三個(gè)文件:stdin、stdout、stderr。

    • 當(dāng)進(jìn)程運(yùn)行結(jié)束,操作系統(tǒng)自動(dòng)關(guān)閉三個(gè)文件。

channel

  • channel 是 Go 語(yǔ)言中的一個(gè) 核心類型,可以把它看成管道(通道 FIFO)。并發(fā)核心單元通過它就可以發(fā)送或者接收數(shù)據(jù)進(jìn)行通訊。

  • channel 是一個(gè)數(shù)據(jù)類型,只要用來(lái)解決協(xié)程的同步問題以及協(xié)程之間數(shù)據(jù)共享(數(shù)據(jù)傳遞)的問題。

  • goroutine 運(yùn)行在相同的地址空間,因此訪問共享內(nèi)存必須做好同步。goroutine 奉行通過通信來(lái)共享內(nèi)存,而不是共享內(nèi)存來(lái)通信

  • 引用類型 channel 可用于多個(gè) goroutine 通訊。其內(nèi)部實(shí)現(xiàn)了同步,確保并發(fā)安全。

goroutine.jpg

定義 channel 變量

chan 是創(chuàng)建 channel 所需使用的關(guān)鍵字。Type 代表指定 channel 手法數(shù)據(jù)的類型。

make(chan Type)     // 等價(jià)于 make(chan Type, 0)
make(chan Type, capacity)

和 map 類似,channel 也是一個(gè)對(duì)應(yīng) make 創(chuàng)建的底層數(shù)據(jù)結(jié)構(gòu)的引用

和其它的引用類型一樣,channel 的零值也是 nil。

賦值 channel 或者用于函數(shù)參數(shù)傳遞,都是使用的 channel 的引用。

當(dāng)闡述 capacity = 0 時(shí),channel 是無(wú)緩沖阻塞讀寫的;當(dāng) capacity > 0 時(shí),channel 有緩沖、是非阻塞的,直到寫滿 capacity 個(gè)元素才阻塞寫入。

channel 非常像生活中的管道,一邊可以存放東西,另一邊可以取出東西。channel 通過操作符 <- 來(lái)接收和發(fā)送數(shù)據(jù):

channel := make(chan int)
channel <- value        // 寫端(傳入端):發(fā)送 value 到 channel
x := <- channel         // 讀端(傳出端):從 channel 中接收數(shù)據(jù),并賦值給 x
x, ok := <- channel     // 功能同上,同時(shí)檢查通道是否已關(guān)閉或者是否為空

默認(rèn)情況下,channel 接收和發(fā)送數(shù)據(jù)都是阻塞的,除非另一端已經(jīng)準(zhǔn)備好,這樣就使得 goroutine 同步變得更加簡(jiǎn)單,而不需要顯示 lock

無(wú)緩沖 channel

make(chan Type)     // 等價(jià)于 make(chan Type, 0)

無(wú)緩沖的通道(unbuffered channel):是指在接收前沒有能力保存任何值的通道。

如果沒有指定緩沖區(qū)容量,那么該通道就是同步的,因此會(huì)阻塞到發(fā)送者準(zhǔn)備好發(fā)送和接收者準(zhǔn)備好接收,才解除阻塞。

無(wú)緩沖的goroutine.png

有緩沖 channel

make(chan Type, capacity)       // capacity > 0

有緩沖的通道(buffered channel)是一種在被接收前能存儲(chǔ)一個(gè)或者多個(gè)數(shù)據(jù)值的通道。

  • 這種類型的通道并不強(qiáng)制要求 goroutine 之間必須同時(shí)完成發(fā)送和接收。通道會(huì)阻塞發(fā)送和接收動(dòng)作的條件也不同。
    • 只要通道中沒有要接收的值時(shí),接收動(dòng)作才會(huì)阻塞。
    • 只要通道沒有可用緩沖區(qū)容納被發(fā)送的值時(shí),發(fā)送動(dòng)作才會(huì)阻塞。

如果給定一個(gè)緩沖區(qū)容量,通道就是異步的。只要緩沖區(qū)有未使用空間用于發(fā)送數(shù)據(jù),或還包含可以接收的數(shù)據(jù),那么其通信就會(huì)無(wú)阻塞的進(jìn)行。

有緩沖的goroutine.png

有緩沖和無(wú)緩沖 channel 的區(qū)別

  • 無(wú)緩沖的通道保證進(jìn)行發(fā)送和接收的 goroutine 會(huì)在同一時(shí)間進(jìn)行數(shù)據(jù)交換;有緩沖的通道沒有這種保證。

關(guān)閉 channel

如果發(fā)送者知道,沒有更多的值要發(fā)送到 channel 的話,那么有必要讓接收者也能及時(shí)知道沒有多余的值可接受,因此接收者可以停止不必要的接收等待??梢酝ㄟ^內(nèi)置的 close() 函數(shù)來(lái)關(guān)閉 channel。

package main

import (
    "fmt"
)

func main() {
    c := make(chan int)

    go func() {
        for i := 0; i < 5; i++ {
            c <- i
        }
        //把 close(c) 注釋掉,程序會(huì)一直阻塞在 if data, ok := <-c; ok 那一行
        close(c)
    }()

    for {
        //ok為true說(shuō)明channel沒有關(guān)閉,為false說(shuō)明管道已經(jīng)關(guān)閉
        if data, ok := <-c; ok {
            fmt.Println(data)
        } else {
            break
        }
        }
        
        // 可以使用 range 來(lái)迭代不斷操作 channel
        // for data := range c {
    //     fmt.Println(data)
    // }

    fmt.Println("Finished")
}

注意

  • channel 不像文件一樣需要經(jīng)常去關(guān)閉,只有當(dāng)你確實(shí)沒有任何發(fā)送數(shù)據(jù)了,或者你想顯示的結(jié)束 range 循環(huán)之類的,才去關(guān)閉 channel
  • 關(guān)閉 channel 后,無(wú)法向 channel 再發(fā)送數(shù)據(jù)(引發(fā) panic 錯(cuò)誤后導(dǎo)致接收立即返回零值)
  • 關(guān)閉 channel 后,可以繼續(xù)從 channel 接收數(shù)據(jù)
  • 對(duì)于 nil channel,無(wú)論收發(fā)都會(huì)被阻塞。

單向 channel 及應(yīng)用

默認(rèn)情況下,通道 channel 是雙向的。也就是,既可以往里面發(fā)送數(shù)據(jù),也可以從里面接收數(shù)據(jù)。

但是,我們經(jīng)常見一個(gè)通道作為參數(shù)進(jìn)行傳遞而只希望對(duì)方是單向使用的,要么只讓它發(fā)送數(shù)據(jù),要么只讓它接收數(shù)據(jù),這時(shí)候我們可以指定通道的方向

channel.png
var ch1 chan int    // ch1 是一個(gè)正常的 channel,是雙向的
var ch2 chan<- float64  // ch2 是單向的 channel,只用于寫 float64 數(shù)據(jù)
var ch3 <-chan int      // ch3 是單向 channel,只用于讀 int 數(shù)據(jù)
  • chan <-:表示數(shù)據(jù)進(jìn)入管道,要把數(shù)據(jù)寫進(jìn)管道,對(duì)于調(diào)用者就是輸出。
  • <- chan:表示數(shù)據(jù)從管道出來(lái),對(duì)于調(diào)用者就是得到管道的數(shù)據(jù),就是輸入。

可以將 channel 隱式轉(zhuǎn)換為單向隊(duì)列,只收或者只發(fā),不能將單向 channel 轉(zhuǎn)換為普通 channel:

ch := make(chan int, 3)
var send chan<- int = ch    // send-only
var recv <-chan int = ch    // receive-only
send <- 1
//<-send //invalid operation: <-send (receive from send-only type chan<- int)
<-recv
//recv <- 2 //invalid operation: recv <- 2 (send to receive-only type <-chan int)

//不能將單向 channel 轉(zhuǎn)換為普通 channel
d1 := (chan int)(send) //cannot convert send (type chan<- int) to type chan int
d2 := (chan int)(recv) //cannot convert recv (type <-chan int) to type chan int
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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