【go語言學(xué)習(xí)】通道channel

通道channel被認(rèn)為是goroutine通信的管道。類似于水管里的水可以從一端流向另一端古涧,數(shù)據(jù)可以從一端發(fā)送到另一端,通過通道接收。

當(dāng)多個goroutine間想實現(xiàn)共享數(shù)據(jù)時闪金,可以使用傳統(tǒng)的同步機(jī)制(sync包的方法),但是go語言強(qiáng)烈建議使用channel通道來實現(xiàn)goroutine之間的通信论颅。

“不要通過共享內(nèi)存來通信哎垦,而應(yīng)該通過通信來共享內(nèi)存”這是一句風(fēng)靡golang社區(qū)的經(jīng)典語言。

Go語言中恃疯,要傳遞某個數(shù)據(jù)給另一個goroutine(協(xié)程)漏设,可以把這個數(shù)據(jù)封裝成一個對象,然后把這個對象的指針傳入某個channel中今妄,另外一個goroutine從這個channel中讀出這個指針郑口,并處理其指向的內(nèi)存對象鸳碧。Go從語言層面保證同一個時間只有一個goroutine能夠訪問channel里面的數(shù)據(jù),為開發(fā)者提供了一種優(yōu)雅簡單的工具犬性,所以Go的做法就是使用channel來通信瞻离,通過通信來傳遞內(nèi)存數(shù)據(jù),使得內(nèi)存數(shù)據(jù)在不同的goroutine中傳遞乒裆,而不是使用共享內(nèi)存來通信套利。

一、什么是通道

1缸兔、通道的概念

通道是什么日裙,通道就是goroutine之間的通道。它可以讓goroutine之間相互通信惰蜜。

每個通道都有與其相關(guān)的類型昂拂。該類型是通道允許傳輸?shù)臄?shù)據(jù)類型。

通道的零值為nil抛猖。nil通道沒有任何用處格侯,因此通道必須使用類似于map和切片的方法來定義。

2财著、通道的聲明

聲明通道联四,和聲明變量是一樣的

// 聲明通道
var 通道名稱 chan 數(shù)據(jù)類型
// 初始化
通道名稱 = make(chan 數(shù)據(jù)類型)

示例代碼:

package main

import "fmt"

func main() {
    var c chan int
    if c == nil {
        fmt.Println("c聲明但是沒有初始化,是nil")
        c = make(chan int)
        fmt.Println(c == nil)
        fmt.Printf("%T", c)
    }
}

運行結(jié)果

c聲明但是沒有初始化撑教,是nil
false
chan int

也可以用簡短定義的方式聲明通道

通道名稱 := make(chan 數(shù)據(jù)類型)
3朝墩、通道數(shù)據(jù)類型

channel是引用類型的數(shù)據(jù),在作為參數(shù)傳遞時伟姐, 傳遞的是內(nèi)存地址收苏。

package main

import "fmt"

func main() {
    c := make(chan int)
    fmt.Printf("main:%T, %p\n", c, c)
    test(c)
}

func test(c chan int) {
    fmt.Printf("test:%T, %p\n", c, c)
}

運行結(jié)果

main:chan int, 0xc00001a0c0
test:chan int, 0xc00001a0c0

二、channel的使用

1愤兵、發(fā)送和接收

發(fā)送和接受的語法

// 讀取數(shù)據(jù)
data := <-chan
// 寫入數(shù)據(jù)
chan <- data
2鹿霸、發(fā)送和接收默認(rèn)是阻塞的

一個通道接收和發(fā)送數(shù)據(jù)默認(rèn)是阻塞的。也就是說秆乳,當(dāng)一個數(shù)據(jù)被發(fā)送到通道時懦鼠,在發(fā)送語句中被阻塞,直到另一個goroutine從該通道讀取數(shù)據(jù)屹堰,才解除阻塞肛冶。相應(yīng)地,當(dāng)從通道中讀取數(shù)據(jù)時扯键,讀取被阻塞淑趾,直到一個goroutine向該通道中寫入數(shù)據(jù)。

這些特性可以幫助goroutines有效地進(jìn)行通信忧陪。

示例代碼:

package main

import "fmt"

func main() {
    c := make(chan bool)
    go func() {
        for i := 0; i < 5; i++ {
            fmt.Println("i = ", i)
        }
        c <- true
    }()
    data := <-c
    fmt.Println(data)
    fmt.Println("main over")
}

運行結(jié)果

i =  0
i =  1
i =  2
i =  3
i =  4
true
main over
3扣泊、死鎖

只向通道中讀取或?qū)懭霐?shù)據(jù),而不寫入或讀取數(shù)據(jù)嘶摊,就會造成阻塞延蟹,產(chǎn)生死鎖。

示例代碼:

package main

func main() {
    c := make(chan bool)
    // fatal error: all goroutines are asleep - deadlock!
    c <- true
}
4叶堆、通道使用注意事項:
  • 用于goroutine阱飘,傳遞消息的。
  • 通道虱颗,每個都有相關(guān)聯(lián)的數(shù)據(jù)類型,nil chan沥匈,不能使用,類似于nil map忘渔,不能直接存儲鍵值對高帖。
  • 使用通道傳遞數(shù)據(jù):<-
    chan <- data,發(fā)送數(shù)據(jù)到通道畦粮,向通道中寫數(shù)據(jù)
    data <- chan散址,從通道中獲取數(shù)據(jù),從通道中讀數(shù)據(jù)
  • 阻塞:
    發(fā)送數(shù)據(jù):chan <- data宣赔,阻塞的预麸,直到另一條goroutine,讀取數(shù)據(jù)來解除阻塞
    讀取數(shù)據(jù):data <- chan儒将,也是阻塞的吏祸。直到另一條goroutine,寫出數(shù)據(jù)解除阻塞钩蚊。
  • 本身channel就是同步的贡翘,意味著同一時間,只能有一條goroutine來操作两疚。
5床估、關(guān)閉通道

發(fā)送者可以通過關(guān)閉通道,來通知接收方不會有更多的數(shù)據(jù)被發(fā)送到channel上诱渤。

close(chan)

接收者可以在接收來自通道的數(shù)據(jù)時使用額外的變量來檢查通道是否已經(jīng)關(guān)閉丐巫。

// ok 為true,表示成功的從通道中讀取數(shù)據(jù)
// ok 為false勺美,表示從關(guān)閉的通道中讀取數(shù)據(jù)递胧,將獲得通道數(shù)據(jù)類型的零值。
v, ok := <- chan

示例代碼:

package main

import "fmt"

func main() {
    controlChan := make(chan bool)
    c := make(chan int)
    go writeData(c, controlChan)
    go readData(c, controlChan)
    _ = <-controlChan
    fmt.Println("main over")
}

func writeData(c chan int, controlChan chan bool) {
    for i := 0; i < 5; i++ {
        c <- i
    }
    close(c)
    controlChan <- true
}

func readData(c chan int, controlChan chan bool) {
    for {
        v, ok := <-c
        if !ok {
            fmt.Println("數(shù)據(jù)讀取完畢")
            break
        }
        fmt.Println("讀取數(shù)據(jù):", v)
    }
    controlChan <- true
}

運行結(jié)果

讀取數(shù)據(jù): 0
讀取數(shù)據(jù): 1
讀取數(shù)據(jù): 2
讀取數(shù)據(jù): 3
讀取數(shù)據(jù): 4
數(shù)據(jù)讀取完畢
main over

我們可以循環(huán)從通道上獲取數(shù)據(jù)赡茸,直到通道關(guān)閉缎脾。for循環(huán)的for range形式可用于從通道接收值,直到它關(guān)閉為止占卧。

示例代碼:

package main

import (
    "fmt"
)

func main() {
    c := make(chan int)
    go writeData(c)
    // for循環(huán)的for range形式可用于從通道接收值遗菠,直到它關(guān)閉為止联喘。
    for v := range c {
        fmt.Println("讀取數(shù)據(jù):", v)
    }
    fmt.Println("main..over.....")
}
func writeData(c chan int) {
    for i := 0; i < 5; i++ {
        c <- i
    }
    close(c)
}

三、緩沖通道

非緩沖通道

c := make(chan T)

緩沖通道

c := make(chan T辙纬,cap)

示例代碼:

package main

import (
    "fmt"
)

func main() {
    c := make(chan int, 3)
    go writeData(c)
    // for循環(huán)的for range形式可用于從通道接收值豁遭,直到它關(guān)閉為止。
    for v := range c {
        fmt.Println("\t讀取數(shù)據(jù):", v)
    }
    fmt.Println("main..over.....")
}
func writeData(c chan int) {
    for i := 1; i <= 6; i++ {
        c <- i
        fmt.Println("寫入數(shù)據(jù): ", i)
    }
    close(c)
}

運行結(jié)果:

寫入數(shù)據(jù):  1
寫入數(shù)據(jù):  2
寫入數(shù)據(jù):  3
寫入數(shù)據(jù):  4
        讀取數(shù)據(jù): 1
        讀取數(shù)據(jù): 2
        讀取數(shù)據(jù): 3
        讀取數(shù)據(jù): 4
        讀取數(shù)據(jù): 5
寫入數(shù)據(jù):  5
寫入數(shù)據(jù):  6
        讀取數(shù)據(jù): 6
main..over.....

四贺拣、定向通道

// 雙向通道
c1 := make(chan T)
// 只寫通道
c2 := make(chan<- T)
// 只讀通道
c3 := make(<-chan T)

定義一個單向(定向)通道是沒有意義的蓖谢,單向通道往往用作函數(shù)的參數(shù)。
示例代碼:

package main

import (
    "fmt"
)

func main() {
    c := make(chan int, 3)
    done := make(chan bool)
    go writeData(c, done)
    go readData(c, done)
    <-done
    fmt.Println("main..over.....")
}
func writeData(c chan<- int, done chan bool) {
    for i := 1; i <= 6; i++ {
        c <- i
        fmt.Println("寫入數(shù)據(jù): ", i)
    }
    close(c)
    done <- true
}
func readData(c <-chan int, done chan bool) {
    for v := range c {
        fmt.Println("\t讀取數(shù)據(jù):", v)
    }
    done <- true
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末譬涡,一起剝皮案震驚了整個濱河市闪幽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌涡匀,老刑警劉巖盯腌,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異渊跋,居然都是意外死亡腊嗡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門拾酝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來燕少,“玉大人,你說我怎么就攤上這事蒿囤】兔牵” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵材诽,是天一觀的道長底挫。 經(jīng)常有香客問我,道長脸侥,這世上最難降的妖魔是什么建邓? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮睁枕,結(jié)果婚禮上官边,老公的妹妹穿的比我還像新娘。我一直安慰自己外遇,他們只是感情好注簿,可當(dāng)我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著跳仿,像睡著了一般诡渴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上菲语,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天妄辩,我揣著相機(jī)與錄音惑灵,去河邊找鬼。 笑死恩袱,一個胖子當(dāng)著我的面吹牛泣棋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播畔塔,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鸯屿!你這毒婦竟也來了澈吨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤寄摆,失蹤者是張志新(化名)和其女友劉穎谅辣,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體婶恼,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡桑阶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了勾邦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚣录。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖眷篇,靈堂內(nèi)的尸體忽然破棺而出萎河,到底是詐尸還是另有隱情,我是刑警寧澤蕉饼,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布虐杯,位于F島的核電站,受9級特大地震影響昧港,放射性物質(zhì)發(fā)生泄漏擎椰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望勋眯。 院中可真熱鬧蹈丸,春花似錦、人聲如沸休弃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽塔猾。三九已至,卻和暖如春稽坤,著一層夾襖步出監(jiān)牢的瞬間丈甸,已是汗流浹背糯俗。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留睦擂,地道東北人得湘。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像顿仇,于是被迫代替她去往敵國和親淘正。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,901評論 2 355