Go學習筆記-Channel最佳實踐之基本規(guī)則【譯】

channel[通道]是golang的一種重要特性皇耗,正是因為channel的存在才使得golang不同于其它語言色迂。channel使得并發(fā)編程變得簡單容易有趣股缸。

channel的概念和語法

一個channel可以理解為一個先進先出的消息隊列验游。channel用來在協程[goroutine]之間傳遞數據幢炸,準確的說泄隔,是用來傳遞數據的所有權。一個設計良好的程序應該確保同一時刻channel里面的數據只會被同一個協程擁有宛徊,這樣就可以避免并發(fā)帶來的數據不安全問題[data races]佛嬉。

channel的類型

像數組、切片和字典一樣闸天,channel類型是一種組合類型暖呕,每一種channel類型都對應著一種簡單的數據類型。比如元素的類型是string苞氮,那么對應的channel類型就是chan string湾揽,進入channel的數據也就必須是string類型的值。

官方的go編譯器限制channel里的單個元素最多65535個字節(jié),也就是說如果channel緩沖數組里面容納的是struct库物,那這個struct的size不能大過65535霸旗。盡管如此,我們也不應該傳遞體積過大的元素值戚揭,因為channel的數據從進入到流出會涉及到數據拷貝操作定硝。如果元素體積過大,最好的方法還是使用傳遞指針來取代傳遞值毫目。

channel類型是可以帶有方向的,假設T是一種類型

  1. chan T是雙向channel類型诲侮,編譯器允許對雙向channel同時進行發(fā)送和接收镀虐。
  2. chan<- T是只寫channel類型,編譯器只允許往channel里面發(fā)送數據沟绪。
  3. <-chan T是只讀channel類型刮便,編輯器只允許從channel里面接收數據。

雙向類型的channel绽慈,可以被強制轉換成只讀channel或者是只寫channel恨旱,但是反過來卻不行,只讀和只寫channel是不可以轉換成雙向channel的坝疼。

channel類型的零值形式稱為空channel搜贤。一個非空channel類型必須通過make關鍵字進行創(chuàng)建。例如make(chan int, 10)將會創(chuàng)建出一個可以容納10個int值的channel钝凶。第二個整形的參數值代表的就是channel可以容納數據的大小仪芒,如果不提供這個參數值,那默認值就是零耕陷。

var ch chan string; // nil channel
ch := make(chan string); // zero channel
ch := make(chan string, 10); // buffered channel

channel里面的value buffer的容量也就是channel的容量掂名。channel的容量為零表示這是一個阻塞型通道,非零表示緩沖型通道[非阻塞型通道]哟沫。

channel內部結構

每個channel內部實現都有三個隊列

  1. 接收消息的協程隊列饺蔑。這個隊列的結構是一個限定最大長度的鏈表,所有阻塞在channel的接收操作的協程都會被放在這個隊列里嗜诀。
  2. 發(fā)送消息的協程隊列猾警。這個隊列的結構也是一個限定最大長度的鏈表。所有阻塞在channel的發(fā)送操作的協程也都會被放在這個隊列里裹虫。
  3. 環(huán)形數據緩沖隊列肿嘲。這個環(huán)形數組的大小就是channel的容量。如果數組裝滿了筑公,就表示channel滿了雳窟,如果數組里一個值也沒有,就表示channel是空的。對于一個阻塞型channel來說封救,它總是同時處于即滿又空的狀態(tài)拇涤。

一個channel被所有使用它的協程所引用,也就是說誉结,只要這兩個裝了協程的隊列長度大于零鹅士,那么這個channel就永遠不會被垃圾回收。另外惩坑,協程本身如果阻塞在channel的讀寫操作上掉盅,這個協程也永遠不會被垃圾回收,即使這個channel只會被這一個協程所引用以舒。

channel的使用

channel支持以下操作

  1. 使用cap(ch)函數查詢channel的容量趾痘,cap是golang的內置函數
  2. 使用len(ch)函數查詢channel內部的數據長度,len函數也是內置的蔓钟,表面上這個函數很有意義永票,但實際上它很少用。
  3. 使用close(ch)關閉channel滥沫,close也是內置函數侣集。一個非空channel只能夠被關閉一次,如果關閉一個已經被關閉的或者是關閉一個空channel將會引發(fā)panic兰绣。另外關閉一個只讀channel是非法的世分,編譯器直接報錯。
  4. 使用ch <- v發(fā)送一個值v到channel缀辩。發(fā)送值到channel可能會有多種結果罚攀,即可能成功,也可能阻塞雌澄,甚至還會引發(fā)panic斋泄,取決于當前channel在什么狀態(tài)。
  5. 使用 v, ok <- ch 接收一個值镐牺。第二個遍歷ok是可選的炫掐,它表示channel是否已關閉。接收值只會又兩種結果睬涧,要么成功要么阻塞募胃,而永遠也不會引發(fā)panic。

所有的這些操作都是同步的協程安全的畦浓,不需要加任何其它同步控制痹束。

For-Range

for-range語法可以用到通道上。循環(huán)會一直接收channel里面的數據讶请,直到channel關閉祷嘶。不同于array/slice/map上的for-range,channel的for-range只允許有一個變量。

for v = range aChannel {
    // use v
}

等價于

for {
    v, ok = <-aChannel
    if !ok {
        break
    }
    // use v
}

注意论巍,for-range對應的channel不能是只寫channel烛谊。

Select-Cases

select塊是為channel特殊設計的語法,它和switch語法非常相近嘉汰。分支上它們都可以有多個case塊和做多一個default塊丹禀,但是也有很多不同

  1. select 到 括號{之間不得有任何表達式

  2. fallthrough關鍵字不能用在select里面

  3. 所有的case語句要么是channel的發(fā)送操作,要么就是channel的接收操作

  4. select里面的case語句是隨機執(zhí)行的鞋怀,而不能是順序執(zhí)行的双泪。設想如果第一個case語句對應的channel是非阻塞的話,case語句的順序執(zhí)行會導致后續(xù)的case語句一直得不到執(zhí)行除非第一個case語句對應的channel里面的值都耗盡了密似。

  5. 如果所有case語句關聯的操作都是阻塞的攒读,default分支就會被執(zhí)行。如果沒有default分支辛友,當前goroutine就會阻塞,當前的goroutine會掛接到所有關聯的channel內部的協程隊列上剪返。 所以說單個goroutine是可以同時掛接到多個channel上的废累,甚至可以同時掛接到同一個channel的發(fā)送協程隊列和接收協程隊列上。當一個阻塞的goroutine拿到了數據接觸阻塞的時候脱盲,它會從所有相關的channel隊列中移除掉邑滨。

channel簡單規(guī)則表,下標的活躍Channel表示即非空又非關閉的Channel
image

channel規(guī)則詳細解釋

空channel

  1. 關閉一個空channel會導致當前goroutine引發(fā)panic
  2. 向一個空channel發(fā)送值會導致當前的goroutine阻塞
  3. 從一個空channel接收值也會導致當前的goroutine阻塞

在空channel上的調用len和cap函數都統一返回零。

已關閉的Channel

  1. 關閉一個已關閉的channel會引發(fā)panic

  2. 向一個已關閉的channel發(fā)送值會引發(fā)panic钱反。當這種send操作處于select塊里面的case語句上時掖看,它會隨時導致select語句引發(fā)panic。

  3. 從一個已關閉的channel上接收值既不會阻塞也不能panic面哥,它一直能成功返回哎壳。只是返回的第二個值ok永遠是false,表示接收到的v是在channel關閉之后拿到的尚卫,對應得值也是相應元素類型的零值归榕。可以無限循環(huán)從已關閉的channel上接收值吱涉。

活躍的Channel

  1. 關閉操作

    1. 從channel的接收協程隊列中移除所有的goroutine刹泄,并喚醒它們。
    2. 從channel的接收協程隊列中移除所有的goroutine怎爵,并喚醒它們特石。
    3. 一個已關閉的channel內部的緩沖數組可能不是空的,沒有接收的這些值會導致channel對象永遠不會被垃圾回收鳖链。
  2. 發(fā)送操作

    1. 如果是阻塞型channel姆蘸,那就從channel的接收協程隊列中移出第一個協程,然后把發(fā)送的值直接遞給這個協程。
    2. 如果是阻塞型channel乞旦,并且channel的接收協程隊列是空的贼穆,那么當前的協程將會阻塞,并進入到channel的發(fā)送協程隊列里兰粉。
    3. 如果是緩沖型channel故痊,并且緩沖數組里還有空間,那么將發(fā)送的值添加到數組最后玖姑,當前協程不阻塞愕秫。
    4. 如果是緩沖型channel,并且緩沖數組已經滿了焰络,那么當前的協程將會阻塞戴甩,并進入到channel的發(fā)送協程隊列中。
  3. 接收操作

    1. 如果是緩沖型channel闪彼,并且緩沖數組有值甜孤,那么當前的協程不會阻塞,直接從數組中拿出第一個值畏腕。如果發(fā)送隊列非空缴川,還需要將隊列中的第一個goroutine喚醒。
    2. 如果是阻塞型channel描馅,并且發(fā)送隊列非空的話把夸,那么喚醒發(fā)送隊列第一個協程,該協程會將發(fā)送的值直接遞給接收的協程铭污。
    3. 如果是緩沖型channel恋日,并且緩沖數組為空,或者是阻塞型channel嘹狞,并且發(fā)送協程隊列為空岂膳,那么當前協程將會阻塞,并加入到channel的接收協程隊列中磅网。

總結

根據以上規(guī)則闷营,我們可以得出以下結論:

  • 如果channel關閉了,那么它的接收和發(fā)送協程隊列必然空了知市,但是它的緩沖數組可能還沒有空傻盟。
  • channel的接收協程隊列和緩沖數組,同一個時間必然有一個是空的
  • channel的緩沖數組如果未滿嫂丙,那么它的發(fā)送協程隊列必然是空的
  • 對于緩沖型channel娘赴,同一時間它的接收和發(fā)送協程隊列,必然有一個是空的
  • 對于非緩沖型channel跟啤,一般來說同一時間它的接收和發(fā)送協程隊列诽表,也必然有一個是空的唉锌,但是有一個例外,那就是當它的發(fā)送操作和接收操作在同一個select塊里出現的時候竿奏,兩個隊列都不是空的袄简。
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市泛啸,隨后出現的幾起案子绿语,更是在濱河造成了極大的恐慌,老刑警劉巖候址,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吕粹,死亡現場離奇詭異,居然都是意外死亡岗仑,警方通過查閱死者的電腦和手機匹耕,發(fā)現死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來荠雕,“玉大人稳其,你說我怎么就攤上這事≌ū埃” “怎么了既鞠?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長矾兜。 經常有香客問我,道長患久,這世上最難降的妖魔是什么椅寺? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮蒋失,結果婚禮上返帕,老公的妹妹穿的比我還像新娘。我一直安慰自己篙挽,他們只是感情好荆萤,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著铣卡,像睡著了一般链韭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上煮落,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天敞峭,我揣著相機與錄音,去河邊找鬼蝉仇。 笑死旋讹,一個胖子當著我的面吹牛殖蚕,可吹牛的內容都是我干的。 我是一名探鬼主播沉迹,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼睦疫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鞭呕?” 一聲冷哼從身側響起蛤育,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎琅拌,沒想到半個月后缨伊,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡进宝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年刻坊,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片党晋。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡谭胚,死狀恐怖,靈堂內的尸體忽然破棺而出未玻,到底是詐尸還是另有隱情灾而,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布扳剿,位于F島的核電站旁趟,受9級特大地震影響,放射性物質發(fā)生泄漏庇绽。R本人自食惡果不足惜锡搜,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瞧掺。 院中可真熱鬧耕餐,春花似錦、人聲如沸辟狈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哼转。三九已至明未,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間壹蔓,已是汗流浹背亚隅。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留庶溶,地道東北人煮纵。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓懂鸵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親行疏。 傳聞我的和親對象是個殘疾皇子匆光,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內容