Go Channel最佳實務之基本規則

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

channel[通道]是golang的一種重要特性,正是因為channel的存在才使得golang不同於其它語言。channel使得並發編程變得簡單容易有趣。

channel的概念和文法

一個channel可以理解為一個先進先出的訊息佇列。channel用來在協程[goroutine]之前傳遞資料,準確的說,是用來傳遞資料的所有權。一個設計良好的程式應該確保同一時刻channel裡面的資料只會被同一個協程擁有,這樣就可以避免並髮帶來的資料不安全問題[data races]。

channel的類型

像數組、切片和字典一樣,channel類型是一種組合類別型,每一種channel類型都對應著一種簡單的資料類型。比如元素的類型是string,那麼對應的channel類型就是chan string,進入channel的資料也就必須是string類型的值。

官方的go編譯器限制channel裡的單個元素最多65535個位元組,也就是說如果channel緩衝數組裡面容納的是struct,那這個struct的size不能大過65535。儘管如此,我們也不應該傳遞體積過大的元素值,因為channel的資料從進入到流出會涉及到資料拷貝操作。如果元素體積過大,最好的方法還是使用傳遞指標來取代傳遞值。

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

  1. chan T是雙向channel類型,編譯器允許對雙向channel同時進行發送和接收。
  2. chan<- T是唯寫channel類型,編譯器只允許往channel裡面發送資料。
  3. <-chan T是唯讀channel類型,編輯器只允許從channel裡面接收資料。

雙向類型的channel,可以被強制轉換成唯讀channel或者是唯寫channel,但是反過來卻不行,唯讀和唯寫channel是不可以轉換成雙向channel的。

channel類型的零值形式稱為空白channel。一個非空channel類型必須通過make關鍵字進行建立。例如make(chan int, 10)將會建立出一個可以容納10個int值的channel。第二個整形的參數值代表的就是channel可以容納資料的大小,如果不提供這個參數值,那預設值就是零。

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

channel裡面的value buffer的容量也就是channel的容量。channel的容量為零表示這是一個阻塞型通道,非零表示緩衝型通道[非阻塞型通道]。

channel內部結構

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

  1. 接收訊息的協程隊列。這個隊列的結構是一個限定最大長度的鏈表,所有阻塞在channel的接收操作的協程都會被放在這個隊列裡。
  2. 發送訊息的協程隊列。這個隊列的結構也是一個限定最大長度的鏈表。所有阻塞在channel的發送操作的協程也都會被放在這個隊列裡。
  3. 環形資料緩衝隊列。這個環形數組的大小就是channel的容量。如果數組裝滿了,就表示channel滿了,如果數組裡一個值也沒有,就表示channel是空的。對於一個阻塞型channel來說,它總是同時處於即滿又空的狀態。

一個channel被所有使用它的協程所引用,也就是說,只要這兩個裝了協程的隊列長度大於零,那麼這個channel就永遠不會被記憶體回收。另外,協程本身如果阻塞在channel的讀寫操作上,這個協程也永遠不會被記憶體回收,即使這個channel只會被這一個協程所引用。

channel的使用

channel支援以下操作

  1. 使用cap(ch)函數查詢channel的容量,cap是golang的內建函數
  2. 使用len(ch)函數查詢channel內部的資料長度,len函數也是內建的,表面上這個函數很有意義,但實際上它很少用。
  3. 使用close(ch)關閉channel,close也是內建函數。一個非空channel只能夠被關閉一次,如果關閉一個已經被關閉的或者是關閉一個空channel將會引發panic。另外關閉一個唯讀channel是非法的,編譯器直接報錯。
  4. 使用ch <- v發送一個值v到channel。發送值到channel可能會有多種結果,即可能成功,也可能阻塞,甚至還會引發panic,取決於當前channel在什麼狀態。
  5. 使用 v, ok <- ch 接收一個值。第二個遍曆ok是可選的,它表示channel是否已關閉。接收值只會又兩種結果,要麼成功要麼阻塞,而永遠也不會引發panic。

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

For-Range

for-range文法可以用到通道上。迴圈會一直接收channel裡面的資料,直到channel關閉。不同於array/slice/map上的for-range,channel的for-range只允許有一個變數。

for v = range aChannel {// use v}

等價於

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

注意,for-range對應的channel不能是唯寫channel。

Select-Cases

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

  1. select 到 括弧{之間不得有任何錶達式
  2. fallthrough關鍵字不能用在select裡面
  3. 所有的case語句要麼是channel的發送操作,要麼就是channel的接收操作
  4. select裡面的case語句是隨機執行的,而不能是順序執行的。設想如果第一個case語句對應的channel是非阻塞的話,case語句的順序執行會導致後續的case語句一直得不到執行除非第一個case語句對應的channel裡面的值都耗盡了。
  5. 如果所有case語句關聯的操作都是阻塞的,default分支就會被執行。如果沒有default分支,當前goroutine就會阻塞,當前的goroutine會掛接到所有關聯的channel內部的協程隊列上。 所以說單個goroutine是可以同時掛接到多個channel上的,甚至可以同時掛接到同一個channel的發送協程隊列和接收協程隊列上。當一個阻塞的goroutine拿到了資料接觸阻塞的時候,它會從所有相關的channel隊列中移除掉。

channel簡單規則表

下標的活躍Channel表示即非空又非關閉的Channel

channel規則詳細解釋

空channel

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

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

已關閉的Channel

  1. 關閉一個已關閉的channel會引發panic
  2. 向一個已關閉的channel發送值會引發panic。當這種send操作處於select塊裡面的case語句上時,它會隨時導致select語句引發panic。
  3. 從一個已關閉的channel上接收值既不會阻塞也不能panic,它一直能成功返回。只是返回的第二個值ok永遠是false,表示接收到的v是在channel關閉之後拿到的,對應得值也是相應元素類型的零值。可以無限迴圈從已關閉的channel上接收值。

活躍的Channel

  1. 關閉操作
    1. 從channel的接收協程隊列中移除所有的goroutine,並喚醒它們。
    2. 從channel的接收協程隊列中移除所有的goroutine,並喚醒它們。
    3. 一個已關閉的channel內部的緩衝數組可能不是空的,沒有接收的這些值會導致channel對象永遠不會被記憶體回收。
  2. 發送操作
    1. 如果是阻塞型channel,那就從channel的接收協程隊列中移出第一個協程,然後把發送的值直接遞給這個協程。
    2. 如果是阻塞型channel,並且channel的接收協程隊列是空的,那麼當前的協程將會阻塞,並進入到channel的發送協程隊列裡。
    3. 如果是緩衝型channel,並且緩衝數組裡還有空間,那麼將發送的值添加到數組最後,當前協程不阻塞。
    4. 如果是緩衝型channel,並且緩衝數組已經滿了,那麼當前的協程將會阻塞,並進入到channel的發送協程隊列中。
  3. 接收操作
    1. 如果是緩衝型channel,並且緩衝數組有值,那麼當前的協程不會阻塞,直接從數組中拿出第一個值。如果發送隊列非空,還需要將隊列中的第一個goroutine喚醒。
    2. 如果是阻塞型channel,並且發送隊列非空的話,那麼喚醒發送隊列第一個協程,該協程會將發送的值直接遞給接收的協程。
    3. 如果是緩衝型channel,並且緩衝數組為空白,或者是阻塞型channel,並且發送協程隊列為空白,那麼當前協程將會阻塞,並加入到channel的接收協程隊列中。

總結

根據以上規則,我們可以得出以下結論

  1. 如果channel關閉了,那麼它的接收和發送協程隊列必然空了,但是它的緩衝數組可能還沒有空。
  2. channel的接收協程隊列和緩衝數組,同一個時間必然有一個是空的
  3. channel的緩衝數組如果未滿,那麼它的發送協程隊列必然是空的
  4. 對於緩衝型channel,同一時間它的接收和發送協程隊列,必然有一個是空的
  5. 對於非緩衝型channel,一般來說同一時間它的接收和發送協程隊列,也必然有一個是空的,但是有一個例外,那就是當它的發送操作和接收操作在同一個select塊裡出現的時候,兩個隊列都不是空的。

點擊查看英文版原文

閱讀更多相關文章,關注知乎專欄 【碼洞】

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.