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)槿绱俗愿赏瑫r(shí)運(yùn)行成千上萬(wàn)個(gè)并發(fā)任務(wù)之景。Goroutine 比 thread 更易用、更高效膏潮、更輕便锻狗。

Goroutine 的創(chuàng)建

只需要在函數(shù)調(diào)用語(yǔ)句前添加 go 關(guān)鍵字,就可創(chuàng)建并發(fā)執(zhí)行單元焕参。開(kāi)發(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)打開(kāi)三個(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è) 核心類(lèi)型栅表,可以把它看成管道(通道 FIFO)。并發(fā)核心單元通過(guò)它就可以發(fā)送或者接收數(shù)據(jù)進(jìn)行通訊吸重。

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

  • goroutine 運(yùn)行在相同的地址空間嚎幸,因此訪問(wèn)共享內(nèi)存必須做好同步颜矿。goroutine 奉行通過(guò)通信來(lái)共享內(nèi)存,而不是共享內(nèi)存來(lá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ù)的類(lèi)型箍铭。

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

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

和其它的引用類(lèi)型一樣坡疼,channel 的零值也是 nil。

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

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

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

channel := make(chan int)
channel <- value        // 寫(xiě)端(傳入端):發(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):是指在接收前沒(méi)有能力保存任何值的通道澄阳。

如果沒(méi)有指定緩沖區(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ù)值的通道。

  • 這種類(lèi)型的通道并不強(qiáng)制要求 goroutine 之間必須同時(shí)完成發(fā)送和接收肮塞。通道會(huì)阻塞發(fā)送和接收動(dòng)作的條件也不同户侥。
    • 只要通道中沒(méi)有要接收的值時(shí),接收動(dòng)作才會(huì)阻塞峦嗤。
    • 只要通道沒(méi)有可用緩沖區(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ù)交換;有緩沖的通道沒(méi)有這種保證恋谭。

關(guān)閉 channel

如果發(fā)送者知道糠睡,沒(méi)有更多的值要發(fā)送到 channel 的話,那么有必要讓接收者也能及時(shí)知道沒(méi)有多余的值可接受疚颊,因此接收者可以停止不必要的接收等待狈孔。可以通過(guò)內(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沒(méi)有關(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í)沒(méi)有任何發(fā)送數(shù)據(jù)了油挥,或者你想顯示的結(jié)束 range 循環(huán)之類(lèi)的,才去關(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)常見(jiàn)一個(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盲链,只用于寫(xiě) float64 數(shù)據(jù)
var ch3 <-chan int      // ch3 是單向 channel,只用于讀 int 數(shù)據(jù)
  • chan <-:表示數(shù)據(jù)進(jìn)入管道迟杂,要把數(shù)據(jù)寫(xiě)進(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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市浪腐,隨后出現(xiàn)的幾起案子纵揍,更是在濱河造成了極大的恐慌,老刑警劉巖议街,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泽谨,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡特漩,警方通過(guò)查閱死者的電腦和手機(jī)吧雹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)拾稳,“玉大人吮炕,你說(shuō)我怎么就攤上這事》玫茫” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)悍抑。 經(jīng)常有香客問(wèn)我鳄炉,道長(zhǎng),這世上最難降的妖魔是什么搜骡? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任拂盯,我火速辦了婚禮,結(jié)果婚禮上记靡,老公的妹妹穿的比我還像新娘谈竿。我一直安慰自己,他們只是感情好摸吠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布空凸。 她就那樣靜靜地躺著,像睡著了一般寸痢。 火紅的嫁衣襯著肌膚如雪呀洲。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天啼止,我揣著相機(jī)與錄音道逗,去河邊找鬼。 笑死献烦,一個(gè)胖子當(dāng)著我的面吹牛滓窍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播巩那,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼吏夯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了拢操?” 一聲冷哼從身側(cè)響起锦亦,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎令境,沒(méi)想到半個(gè)月后杠园,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡舔庶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年抛蚁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惕橙。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瞧甩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出弥鹦,到底是詐尸還是另有隱情肚逸,我是刑警寧澤爷辙,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站朦促,受9級(jí)特大地震影響膝晾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜务冕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一血当、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧禀忆,春花似錦臊旭、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至乏德,卻和暖如春撤奸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喊括。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工胧瓜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人郑什。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓府喳,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蘑拯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子钝满,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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