Go channel功能詳解

?? 在golang中征冷,channel屬于較為核心的一個(gè)功能锭弊,尤其在go協(xié)程中,channel功能尤為重要。作為goroutine之間通信的一種方式窃页,channel跟Linux系統(tǒng)中的管道/消息隊(duì)列有很多類似之處俺榆。使用channel可以方便地在goroutine之間傳遞數(shù)據(jù)赞枕,此外垮斯,channel還關(guān)聯(lián)了數(shù)據(jù)類型,如int蜀涨、string等等瞎嬉,可以決定確定channel中的數(shù)據(jù)單元。

[TOC]

定義channel

?? Channel類型的定義格式如下厚柳,包括三種類型的定義氧枣。可選的<-代表channel的方向别垮,如果沒有指定方向挑胸,那么Channel就是雙向的,既可以接收數(shù)據(jù)宰闰,也可以發(fā)送數(shù)據(jù)茬贵。

ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .

chan T          // 可以接收和發(fā)送類型為 T 的數(shù)據(jù)
chan<- float64  // 只可以用來發(fā)送 float64 類型的數(shù)據(jù)
<-chan int      // 只可以用來接收 int 類型的數(shù)據(jù)

// <-總是優(yōu)先和最左邊的類型結(jié)合簿透。
chan<- chan int    // 等價(jià) chan<- (chan int)
chan<- <-chan int  // 等價(jià) chan<- (<-chan int)
<-chan <-chan int  // 等價(jià) <-chan (<-chan int)
chan (<-chan int)

?? 和slice、map類似解藻,可以使用make關(guān)鍵字來初始化channel老充。

unbuffered := make(chan int)  //定義無緩沖的整型通道
buffered := make(chan string, 10)  //有緩沖的字符串通道

??上述代碼中定義了一個(gè)無緩沖的channel和一個(gè)有緩沖channel。make的第一個(gè)參數(shù)需要是關(guān)鍵字chan螟左,之后跟著允許通道交換的數(shù)據(jù)的類型啡浊。如果創(chuàng)建的是一個(gè)有緩沖channel,之后還需要在第二個(gè)參數(shù)指定這個(gè)channel的緩沖區(qū)的大小胶背。和其他引用類型一樣巷嚣,channel 的空值為 nil ,使用 == 可以對(duì)類型相同的 channel 進(jìn)行比較钳吟,只有指向相同對(duì)象或同為 nil 時(shí)廷粒,才返回 true
??無緩沖channel默認(rèn)會(huì)阻塞讀取操作红且,有緩沖channel能部分避免阻塞讀取操作坝茎。據(jù)此特性可以實(shí)現(xiàn)很多應(yīng)用場(chǎng)景,后文將會(huì)逐項(xiàng)介紹暇番。

讀寫channel

buffered <- "Gopher" //向channel buffered發(fā)送一個(gè)字符串
value := <-buffered //從channel buffered中接受一個(gè)字符串

??Go使用操作符->實(shí)現(xiàn)channel的讀寫功能嗤放。需要注意的是,在執(zhí)行讀寫操作之前必須先初始化此通道壁酬,否則會(huì)出現(xiàn)永久阻塞的現(xiàn)象次酌。

關(guān)閉channel

??使用go內(nèi)置的close函數(shù)可以關(guān)閉channel,實(shí)際使用中經(jīng)常使用 defer功能舆乔,在程序最后關(guān)閉channel岳服。

close(buffered)

channel用處

gorouting通信

??這一點(diǎn)勿需多言,前面介紹channel時(shí)就提到這一點(diǎn)蜕煌,channel的下述特性均基于此項(xiàng)功能實(shí)現(xiàn)派阱。

gorouting同步

?? 對(duì)于unbuffered channel诬留,缺省情況下發(fā)送和接收會(huì)一直阻塞著斜纪,直至另一方做好準(zhǔn)備。用此特性可以實(shí)現(xiàn)gororutine之間的同步功能文兑,而不必使用顯式的鎖或條件變量盒刚。

package main

import "fmt"

func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    c <- sum // send sum to c
}
func main() {
    s := []int{7, 2, 8, -9, 4, 0}
    c := make(chan int)
    go sum(s[:len(s)/2], c)
    go sum(s[len(s)/2:], c)
    x, y := <-c, <-c // receive from c
    fmt.Println(x, y, x+y)
}

?? 上述代碼執(zhí)行結(jié)果如下

kefin@localhost:~/gopath/src/iotest $ go run sync.go
-5 17 12

?? 上述代碼是官方提供的例子,x, y := <-c, <-c這行代碼會(huì)一直處于阻塞狀態(tài)绿贞,直至計(jì)算結(jié)果發(fā)送到channel中因块。

用于range遍歷

package main

import (
    "fmt"
    "time"
)

func main() {
    go func() {
        time.Sleep(1 * time.Hour)
    }()
    c := make(chan int)
    go func() {
        for i := 0; i < 10; i = i + 1 {
            c <- i
        }
        close(c)
    }()
    for i := range c {
        fmt.Println(i)
    }
    fmt.Println("Finished")
}

?? range c產(chǎn)生的迭代值為Channel中發(fā)送的值,它會(huì)一直迭代知道channel被關(guān)閉籍铁。上面的例子中如果把close(c)注釋掉涡上,程序會(huì)一直阻塞在for …… range那一行趾断。代碼的執(zhí)行結(jié)果為

kefin@localhost:~/gopath/src/iotest $ go run range.go
0
1
2
3
4
5
6
7
8
9
Finished

配合select使用

?? select語句選擇一組可能的send操作和receive操作去處理,它類似switch吩愧,但是只是用來處理通訊(communication)操作芋酌。 它的case可以是send語句,也可以是receive語句雁佳,亦或者default脐帝。receive語句可以將值賦值給一個(gè)或者兩個(gè)變量,最多允許有一個(gè)default case,它可以放在case列表的任何位置糖权,大部分會(huì)將它放在最后堵腹。

package main

import "fmt"

func fibonacci(c, quit chan int) 
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}
func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}

??如果有同時(shí)多個(gè)case去處理,比如同時(shí)有多個(gè)channel可以接收數(shù)據(jù)星澳,那么Go會(huì)偽隨機(jī)的選擇一個(gè)case處理(pseudo-random)疚顷。如果沒有case需要處理,則會(huì)選擇default去處理募判。如果沒有default case荡含,則select語句會(huì)阻塞,直到某個(gè)case需要處理届垫。需要注意的是释液,nil channel上的操作會(huì)一直被阻塞。如果沒有default case装处,只有nil误债,那么channel的select會(huì)一直被阻塞。

??此外妄迁,還可以配合select的超時(shí)處理功能寝蹈,如上所述,沒有case需要處理時(shí)登淘,select語句就會(huì)一直阻塞箫老,此時(shí)通常需要設(shè)置超時(shí)操作來處理超時(shí)的情況。 下面這個(gè)例子我們會(huì)在2秒后往channel c1中發(fā)送一個(gè)數(shù)據(jù)黔州,但是select設(shè)置為1秒超時(shí),因此我們會(huì)打印出timeout 1,而不是result 1耍鬓。

 package main

 import (
     "fmt"
     "time"
 )

 func main() {
     c1 := make(chan string, 1)
     go func() {
         time.Sleep(time.Second * 2)
         c1 <- "result 1"
     }()
     select {
     case res := <-c1:
         fmt.Println(res)
     case <-time.After(time.Second * 1):
         fmt.Println("timeout 1")
     }
 }

??其實(shí)利用的是time.After方法,它返回一個(gè)類型為<-chan Time的單向的channel流妻,在指定的時(shí)間發(fā)送一個(gè)當(dāng)前時(shí)間給返回的channel中牲蜀。執(zhí)行結(jié)果為

kefin@localhost:~/gopath/src/iotest $ go run select_timeout.go
timeout 1

實(shí)現(xiàn)Timer和Ticker

??timer是一個(gè)定時(shí)器,代表未來的一個(gè)單一事件绅这,可以設(shè)置timer需要等待多長(zhǎng)時(shí)間涣达,它提供一個(gè)Channel,在將來的那個(gè)時(shí)間那個(gè)Channel提供了一個(gè)時(shí)間值。下面的例子中第二行會(huì)阻塞2秒鐘左右的時(shí)間度苔,直到時(shí)間到了才會(huì)繼續(xù)執(zhí)行匆篓。當(dāng)然如果只想單純的等待2秒,可以使用time.Sleep(2)來實(shí)現(xiàn)寇窑。

timer1 := time.NewTimer(time.Second * 2)
<-timer1.C
fmt.Println("Timer 1 expired")

你還可以使用timer.Stop來停止[計(jì)時(shí)器]

timer2 := time.NewTimer(time.Second)
go func() {
    <-timer2.C
    fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop()
if stop2 {
    fmt.Println("Timer 2 stopped")
}

??ticker是一個(gè)定時(shí)觸發(fā)的計(jì)時(shí)器奕删,它會(huì)以一個(gè)間隔(interval)往Channel發(fā)送一個(gè)事件(當(dāng)前時(shí)間),而Channel的接收者可以以固定的時(shí)間間隔從Channel中讀取事件疗认。下面的例子中ticker每500毫秒觸發(fā)一次完残,你可以觀察輸出的時(shí)間

ticker := time.NewTicker(time.Millisecond * 500)
go func() {
    for t := range ticker.C {
        fmt.Println("Tick at", t)
    }
}()

同樣,ticker也可以通過Stop方法來停止横漏。一旦它停止谨设,接收者不再會(huì)從channel中接收數(shù)據(jù)了。



channel操作注意事項(xiàng)

  • 關(guān)閉一個(gè)未初始化(nil) 的 channel 或者重復(fù)關(guān)閉同一個(gè)channel均會(huì)產(chǎn)生 panic
  • 向一個(gè)已關(guān)閉的 channel 中發(fā)送消息會(huì)產(chǎn)生 panic
  • 從已關(guān)閉的 channel 讀取消息不會(huì)產(chǎn)生 panic缎浇,且能讀出 channel 中還未被讀取的消息扎拣,若消息均已讀出,則會(huì)讀到類型的零值素跺。
  • 從已關(guān)閉的 channel 中讀取消息永遠(yuǎn)不會(huì)阻塞二蓝,并且會(huì)返回 false ,據(jù)此可判斷 channel 是否關(guān)閉
  • 關(guān)閉 channel 會(huì)產(chǎn)生一個(gè)廣播機(jī)制指厌,所有向 channel 讀取消息的 goroutine 都會(huì)收到消息

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末刊愚,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子踩验,更是在濱河造成了極大的恐慌鸥诽,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件箕憾,死亡現(xiàn)場(chǎng)離奇詭異牡借,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)袭异,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門钠龙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人御铃,你說我怎么就攤上這事碴里。” “怎么了畅买?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵并闲,是天一觀的道長(zhǎng)细睡。 經(jīng)常有香客問我谷羞,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任湃缎,我火速辦了婚禮犀填,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嗓违。我一直安慰自己九巡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布蹂季。 她就那樣靜靜地躺著冕广,像睡著了一般。 火紅的嫁衣襯著肌膚如雪偿洁。 梳的紋絲不亂的頭發(fā)上撒汉,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音涕滋,去河邊找鬼睬辐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛宾肺,可吹牛的內(nèi)容都是我干的溯饵。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼锨用,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼丰刊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起增拥,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤藻三,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后跪者,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棵帽,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年渣玲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逗概。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡忘衍,死狀恐怖逾苫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情枚钓,我是刑警寧澤铅搓,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站搀捷,受9級(jí)特大地震影響星掰,放射性物質(zhì)發(fā)生泄漏多望。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一氢烘、第九天 我趴在偏房一處隱蔽的房頂上張望怀偷。 院中可真熱鬧,春花似錦播玖、人聲如沸椎工。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽维蒙。三九已至,卻和暖如春果覆,著一層夾襖步出監(jiān)牢的瞬間木西,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工随静, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留八千,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓燎猛,卻偏偏與公主長(zhǎng)得像恋捆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子重绷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

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