Go Channel 詳解

0. 引言

channel 是 Go 語言中的一個非常重要的特性塞绿,這篇文章來深入了解一下 channel耳幢。

1. CSP

要想理解 channel 要先知道 CSP 模型袄友。CSP 是 Communicating Sequential Process 的簡稱挖炬,中文可以叫做通信順序進(jìn)程传泊,是一種并發(fā)編程模型,由 Tony Hoare 于 1977 年提出遵岩。簡單來說你辣,CSP 模型由并發(fā)執(zhí)行的實體(線程或者進(jìn)程)所組成座慰,實體之間通過發(fā)送消息進(jìn)行通信京郑,這里發(fā)送消息時使用的就是通道,或者叫 channel普舆。CSP 模型的關(guān)鍵是關(guān)注 channel誊锭,而不關(guān)注發(fā)送消息的實體表悬。Go 語言實現(xiàn)了 CSP 部分理論,goroutine 對應(yīng) CSP 中并發(fā)執(zhí)行的實體炉旷,channel 也就對應(yīng)著 CSP 中的 channel签孔。

2 channel 基礎(chǔ)知識

2.1 創(chuàng)建 channel

channel 使用之前需要通過 make 創(chuàng)建。

unBufferChan := make(chan int)  // 1
bufferChan := make(chan int, N) // 2

上面的方式 1 創(chuàng)建的是無緩沖 channel窘行,方式 2 創(chuàng)建的是緩沖 channel饥追。如果使用 channel 之前沒有 make,會出現(xiàn) dead lock 錯誤罐盔。至于為什么是 dead lock但绕,下文我們從源碼里面看看。

func main() {
    var x chan int
    go func() {
        x <- 1
    }()
    <-x
}
$ go run channel1.go
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive (nil chan)]:
main.main()
    /Users/kltao/code/go/examples/channl/channel1.go:11 +0x60

goroutine 4 [chan send (nil chan)]:
main.main.func1(0x0)
2.2 channel 讀寫操作
ch := make(chan int, 10)

// 讀操作
x <- ch

// 寫操作
ch <- x
2.3 channel 種類

channel 分為無緩沖 channel 和有緩沖 channel惶看。兩者的區(qū)別如下:

  • 無緩沖:發(fā)送和接收動作是同時發(fā)生的捏顺。如果沒有 goroutine 讀取 channel (<- channel),則發(fā)送者 (channel <-) 會一直阻塞纬黎。

    image
  • 緩沖:緩沖 channel 類似一個有容量的隊列幅骄。當(dāng)隊列滿的時候發(fā)送者會阻塞;當(dāng)隊列空的時候接收者會阻塞本今。

    image
2.4 關(guān)閉 channel

channel 可以通過 built-in 函數(shù) close() 來關(guān)閉拆座。

ch := make(chan int)

// 關(guān)閉
close(ch)

關(guān)于關(guān)閉 channel 有幾點需要注意的是:

  • 重復(fù)關(guān)閉 channel 會導(dǎo)致 panic主巍。
  • 向關(guān)閉的 channel 發(fā)送數(shù)據(jù)會 panic。
  • 從關(guān)閉的 channel 讀數(shù)據(jù)不會 panic挪凑,讀出 channel 中已有的數(shù)據(jù)之后再讀就是 channel 類似的默認(rèn)值孕索,比如 chan int 類型的 channel 關(guān)閉之后讀取到的值為 0。

對于上面的第三點躏碳,我們需要區(qū)分一下:channel 中的值是默認(rèn)值還是 channel 關(guān)閉了搞旭。可以使用 ok-idiom 方式菇绵,這種方式在 map 中比較常用肄渗。

ch := make(chan int, 10)
...
close(ch)

// ok-idiom 
val, ok := <-ch
if ok == false {
    // channel closed
}

3 channel 的典型用法

1 goroutine 通信

func main() {
    x := make(chan int)
    go func() {
        x <- 1
    }()
    <-x
}
2 select

select 一定程度上可以類比于 linux 中的 IO 多路復(fù)用中的 select。后者相當(dāng)于提供了對多個 IO 事件的統(tǒng)一管理脸甘,而 Golang 中的 select 相當(dāng)于提供了對多個 channel 的統(tǒng)一管理恳啥。當(dāng)然這只是 select 在 channel 上的一種使用方法。

select {
    case e, ok := <-ch1:
        ...
    case e, ok := <-ch2:
        ...
    default:  
}

值得注意的是 select 中的 break 只能跳到 select 這一層丹诀。select 使用的時候一般配合 for 循環(huán)使用,像下面這樣翁垂,因為正常 select 里面的流程也就執(zhí)行一遍铆遭。這么來看 select 中的 break 就稍顯雞肋了。所以使用 break 的時候一般配置 label 使用沿猜,label 定義在 for 循環(huán)這一層枚荣。


for {
    select {
        ...
    }
}
3 range channel

range channel 可以直接取到 channel 中的值。當(dāng)我們使用 range 來操作 channel 的時候啼肩,一旦 channel 關(guān)閉橄妆,channel 內(nèi)部數(shù)據(jù)讀完之后循環(huán)自動結(jié)束。

func consumer(ch chan int) {
    for x := range ch {
        fmt.Println(x)
        ...
    }
}

func producer(ch chan int) {
  for _, v := range values {
      ch <- v
  }  
}
4 超時控制

在很多操作情況下都需要超時控制祈坠,利用 select 實現(xiàn)超時控制害碾,下面是一個簡單的示例。

select {
  case <- ch:
    // get data from ch
  case <- time.After(2 * time.Second)
    // read data from ch timeout
}

類似的赦拘,上面的 time.After 可以換成其他的任何異郴潘妫控制流。

5 生產(chǎn)者-消費(fèi)者模型

利用緩沖 channel 可以很輕松的實現(xiàn)生產(chǎn)者-消費(fèi)者模型躺同。上面的 range 示例其實就是一個簡單的生產(chǎn)者-消費(fèi)者模型實現(xiàn)阁猜。

4 單向 channel

單向 channel,顧名思義只能寫或讀的 channel蹋艺。但是仔細(xì)一想剃袍,只能寫的 channel,如果不讀其中的值有什么用呢捎谨?其實單向 channel 主要用在函數(shù)聲明中民效。比如隘击。

func foo(ch chan<- int) <-chan int {...}

foo 的形參是一個只能寫的 channel,那么就表示函數(shù) foo 只會對 ch 進(jìn)行寫研铆,當(dāng)然你傳入的參數(shù)可以是個普通 channel埋同。foo 的返回值是一個只能讀的 channel,那么表示 foo 的返回值規(guī)范用法就是只能讀取棵红。這種寫法在 Golang 的原生代碼庫中有非常多的示例凶赁,感興趣的可以去看一下。

// Done returns a channel which is closed if and when this pipe is closed
// with CloseWithError.
func (p *http2pipe) Done() <-chan struct{} {
    p.mu.Lock()
    defer p.mu.Unlock()
    if p.donec == nil {
        p.donec = make(chan struct{})
        if p.err != nil || p.breakErr != nil {

            p.closeDoneLocked()
        }
    }
    return p.donec
}

也許你會說這么寫在功能上和使用普通的 channel 并不會有什么差別逆甜。確實是這樣的虱肄。但是使用單向 channel 編程體現(xiàn)了一種非常優(yōu)秀的編程范式:convention over configuration,中文一般叫做 約定優(yōu)于配置交煞。這種編程范式在 Ruby 中體現(xiàn)的尤為明顯咏窿。

5 總結(jié)

Golang 的 channel 將 goroutine 隔離開,并發(fā)編程的時候可以將注意力放在 channel 上素征。在一定程度上集嵌,這個和消息隊列的解耦功能還是挺像的。上面主要還是介紹了一些 channel 的常規(guī)操作御毅,還有一些奇淫技巧放在參考資料里了根欧。之后的一篇文章還是來看看 channel 的源碼吧,對于更深入地理解 channel 還是挺有用的端蛆。

引自

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凤粗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子今豆,更是在濱河造成了極大的恐慌嫌拣,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呆躲,死亡現(xiàn)場離奇詭異异逐,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)歼秽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門应役,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人燥筷,你說我怎么就攤上這事箩祥。” “怎么了肆氓?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵袍祖,是天一觀的道長。 經(jīng)常有香客問我谢揪,道長蕉陋,這世上最難降的妖魔是什么捐凭? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮凳鬓,結(jié)果婚禮上茁肠,老公的妹妹穿的比我還像新娘。我一直安慰自己缩举,他們只是感情好垦梆,可當(dāng)我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著仅孩,像睡著了一般托猩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辽慕,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天京腥,我揣著相機(jī)與錄音,去河邊找鬼溅蛉。 笑死公浪,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的温艇。 我是一名探鬼主播因悲,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼勺爱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起讯检,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤琐鲁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后人灼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體围段,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年投放,在試婚紗的時候發(fā)現(xiàn)自己被綠了奈泪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡灸芳,死狀恐怖涝桅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情烙样,我是刑警寧澤冯遂,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站谒获,受9級特大地震影響蛤肌,放射性物質(zhì)發(fā)生泄漏壁却。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一裸准、第九天 我趴在偏房一處隱蔽的房頂上張望展东。 院中可真熱鬧,春花似錦炒俱、人聲如沸盐肃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恼蓬。三九已至,卻和暖如春僵芹,著一層夾襖步出監(jiān)牢的瞬間处硬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工拇派, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留荷辕,地道東北人。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓件豌,卻偏偏與公主長得像疮方,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子茧彤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,933評論 2 355

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

  • 作為golang并發(fā)編程思想的重要組成骡显,channel(通道)非常重要,和goroutine(go協(xié)程)一起使用曾掂,...
    smoke_zl閱讀 871評論 0 5
  • Channel是Go中的一個核心類型惫谤,你可以把它看成一個管道,通過它并發(fā)核心單元就可以發(fā)送或者接收數(shù)據(jù)進(jìn)行通訊(c...
    空即是色即是色即是空閱讀 2,339評論 0 1
  • 由淺入深剖析 go channel channel 是 golang 中最核心的 feature 之一珠洗,因此理解 ...
    不智魚閱讀 59,735評論 4 83
  • CSP 要想理解 channel 要先知道 CSP 模型溜歪。CSP 是 Communicating Sequenti...
    _羊羽_閱讀 1,601評論 0 0
  • 協(xié)程,通道 我們在普通程序中要執(zhí)行代碼如下代碼 錯誤使用協(xié)程 由于沒有調(diào)度许蓖,主協(xié)程率先執(zhí)行完畢蝴猪,代碼執(zhí)行已經(jīng)關(guān)閉,...
    seven_son閱讀 2,100評論 0 0