GOLANG:有趣的 CHANNEL 應用

嚴格意義上說览效,本文是我另外一片文章《Golang Funny: Play with Channel》的中文版本。不過夹厌,畢竟是用中文當母語的匪煌,所以就不翻譯了责蝠,重新按照那個內容寫過吧。

channel 是 golang 里相當有趣的一個功能虐杯,在我使用 golang 編碼的經(jīng)驗里玛歌,大部分事件都會是在享受 channel 和 goroutine 配合的樂趣。所以本文主要介紹 channel 的一些有趣的用法擎椰。

這里有 Oling Cat 翻譯的Go編程語言規(guī)范里關于 channel(信道)的描述:

信道提供了一種機制,它在兩個并發(fā)執(zhí)行的函數(shù)之間進行同步创肥,并通過傳遞(與該信道元素類型相符的)值來進行通信达舒。

這個個描述又乏味值朋、又枯燥。在我第一次閱讀的時候巩搏,完全不明白這到底是個什么玩意昨登。事實上,可以認為 channel 是一個管道或者先進先出隊列贯底,非常簡單且輕量丰辣。channel 并不是 Golang 首創(chuàng)的。它同樣作為內置功能出現(xiàn)在其他語言中禽捆。在大多數(shù)情況下笙什,它是一個又大、又笨胚想、又復雜的消息隊列系統(tǒng)的一個功能琐凭。

下面就來一起找點樂子吧!

最常見的方式:生產(chǎn)者/消費者

生產(chǎn)者產(chǎn)生一些數(shù)據(jù)將其放入 channel浊服;然后消費者按照順序统屈,一個一個的從 channel 中取出這些數(shù)據(jù)進行處理。這是最常見的 channel 的使用方式牙躺。當 channel 的緩沖用盡時愁憔,生產(chǎn)者必須等待(阻塞)。換句話說孽拷,若是 channel 中沒有數(shù)據(jù)吨掌,消費者就必須等待了。

這個例子的源代碼在這里乓搬。最好下載到本地運行思犁。

生產(chǎn)者

func producer(c chan int64, max?int) {

????defer close(c)

????for?i:= 0; i < max; i ++ {

????????c <-?time.Now().Unix()

????}

}

生產(chǎn)者生成“max”個 int64 的數(shù)字,并且將其放入 channel “c” 中进肯。需要注意的是激蹲,這里用 defer 在函數(shù)推出的時候關閉了 channel。

消費者

func consumer(c chan int64) {

????var v int64

????ok :=?true

????for?ok {

????????if?v, ok = <-c; ok {

????????????fmt.Println(v)

????????}

????}

}

從 channel 中一個一個的讀取 int64 的數(shù)字江掩,然后將其打印在屏幕上学辱。當 channel 被關閉后,變量“ok”將被設置為“false”环形。

自增長 ID 生成器

當生讓產(chǎn)者可以順序的生成整數(shù)策泣。它就是一個自增長 ID 生成器。我將這個功能封裝成了一個包抬吟。并將其代碼托管在這里萨咕。使用示例可以參考這里的代碼。

type AutoInc?struct?{

????start, step?int

????queue chan?int

????running?bool

}


func New(start, step?int) (ai *AutoInc) {

????ai = &AutoInc{

????????start: start,

????????step: step,

????????running:?true,

????????queue: make(chan?int, 4),

????}

????go ai.process()

????return

}


func (ai *AutoInc) process() {

????defer func() {recover()}()

????for?i := ai.start; ai.running ; i=i+ai.step {

????????ai.queue <- i

????}

}


func (ai *AutoInc) Id()?int?{

????return?<-ai.queue

}


func (ai *AutoInc) Close() {

????ai.running =?false

????close(ai.queue)

}

信號量

信號量也是 channel 的一個有趣的應用火本。這里有一個來自“高效Go編程”的例子危队。你應當讀過了吧聪建?如果還沒有,現(xiàn)在就開始讀吧……

我在 Gearman 服務的 API 包?gearman-go?中使用了信號量茫陆。在?worker/worker.go?的 232 行金麸,在并行的 Worker.exec 的數(shù)量達到 Worker.limit 時,將被阻塞簿盅。

var sem = make(chan?int, MaxOutstanding)


func handle(r *Request) {

????sem <- 1?// 等待放行挥下;

????process(r)?// 可能需要一個很長的處理過程;

????<-sem?// 完成桨醋,放行另一個過程棚瘟。

}


func Serve(queue chan *Request) {

????for?{

????????req := <-queue

????????go handle(req)?// 無需等待 handle 完成。

????}

}

隨機序列生成器

當然可以修改自增長 ID 生成器讨盒。讓生產(chǎn)者生成隨機數(shù)放入 channel解取。不過這挺無聊的,不是嗎返顺?

這里是隨機序列生成器的另一個實現(xiàn)禀苦。靈感來自語言規(guī)范。它會隨機的生成 0/1 序列:

func producer(c chan int64, max?int) {

????defer close(c)

????for?i:= 0; i < max; i ++ {

????????select {?// randomized select

????????????case?c <- 0:

????????????case?c <- 1:

????????}

????}

}

超時定時器

當一個 channel 被 read/write 阻塞時遂鹊,它會被永遠阻塞下去振乏,直到 channel 被關閉,這時會產(chǎn)生一個 panic秉扑。channel 沒有內建用于超時的定時器慧邮。并且似乎也沒有計劃向 channel 添加一個這樣的功能。但在大多數(shù)情況下舟陆,我們需要一個超時機制误澳。例如,由于生產(chǎn)者執(zhí)行的時候發(fā)生了錯誤秦躯,所以沒有向 channel 放入數(shù)據(jù)忆谓。消費者會被阻塞到 channel 被關閉。每次出錯都關閉 channel踱承?這絕對不是一個好主意倡缠。

這里有一個解決方案:

c := make(chan int64, 5)

defer close(c)

timeout := make(chan?bool)

defer close(timeout)

go func() {

????time.Sleep(time.Second)?// 等一秒

????timeout <-?true?// 向超時隊列中放入標志

}()

select {

????case?<-timeout:?// 超時

????????fmt.Println("timeout...")

????case?<-c:?// 收到數(shù)據(jù)

????????fmt.Println("Read a date.")

}

你注意到 select 語句了嗎?哪個 channel 先有數(shù)據(jù)茎活,哪個分支先執(zhí)行昙沦。因此……還需要更多的解釋嗎?

這同樣被使用在gearman-go 的客戶端 API 實現(xiàn)中载荔,第 238 行盾饮。

在本文的英文版本發(fā)布后,@mjq?提醒我說可以用?time.After。在項目中丐谋,這確實是更好的寫法芍碧。我得向他道謝煌珊!同時我也閱讀了?src/pkg/time/sleep.go?第 74 行号俐,time.After 的實現(xiàn)。其內部實現(xiàn)與上面的代碼完全一致定庵。

還有更多……

上面提到的各種有趣的應用當然也可以在其他消息隊列中實現(xiàn)吏饿,不過由于 channel 的簡單和輕量,使得 golang 的 channel 來實現(xiàn)這些有趣的功能具有實際意義蔬浙,并有真實的應用場景猪落。其實病曾,我覺得有趣的 channel 用法遠不止這些

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末熄诡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子办成,更是在濱河造成了極大的恐慌俱病,老刑警劉巖官疲,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異亮隙,居然都是意外死亡途凫,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門溢吻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來维费,“玉大人,你說我怎么就攤上這事促王∠耍” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵蝇狼,是天一觀的道長阅畴。 經(jīng)常有香客問我,道長题翰,這世上最難降的妖魔是什么恶阴? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮豹障,結果婚禮上冯事,老公的妹妹穿的比我還像新娘。我一直安慰自己血公,他們只是感情好昵仅,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般摔笤。 火紅的嫁衣襯著肌膚如雪够滑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天吕世,我揣著相機與錄音彰触,去河邊找鬼。 笑死命辖,一個胖子當著我的面吹牛况毅,可吹牛的內容都是我干的。 我是一名探鬼主播尔艇,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼尔许,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了终娃?” 一聲冷哼從身側響起味廊,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎棠耕,沒想到半個月后余佛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡昧辽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年衙熔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搅荞。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡红氯,死狀恐怖,靈堂內的尸體忽然破棺而出咕痛,到底是詐尸還是另有隱情痢甘,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布茉贡,位于F島的核電站塞栅,受9級特大地震影響,放射性物質發(fā)生泄漏腔丧。R本人自食惡果不足惜放椰,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望愉粤。 院中可真熱鬧砾医,春花似錦、人聲如沸衣厘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至错邦,卻和暖如春探赫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背撬呢。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工伦吠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人倾芝。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓讨勤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親晨另。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內容