Go語言通道的多路複用——同時處理接收和傳送多個通道的資料

2020-07-16 10:04:50
多路複用是通訊和網路中的一個專業術語。多路複用通常表示在一個通道上傳輸多路信號或資料流的過程和技術。

提示

報話機同一時刻只能有一邊進行收或者發的單邊通訊,報話機需要遵守的通訊流程如下:
  • 說話方在完成時需要補上一句“完畢”,隨後放開通話按鈕,從傳送切換到接收狀態,收聽對方說話。
  • 收聽方在聽到對方說“完畢”時,按下通話按鈕,從接收切換到傳送狀態,開始說話。

電話可以在說話的同時聽到對方說話,所以電話是一種多路複用的裝置,一條通訊線路上可以同時接收或者傳送資料。同樣的,網線、光纖也都是基於多路複用模式來設計的,網線、光纖不僅可支援同時收發資料,還支援多個人同時收發資料。

在使用通道時,想同時接收多個通道的資料是一件困難的事情。通道在接收資料時,如果沒有資料可以接收將會發生阻塞。雖然可以使用如下模式進行遍歷,但執行效能會非常差。
for{
    // 嘗試接收ch1通道
    data, ok := <-ch1
    // 嘗試接收ch2通道
    data, ok := <-ch2
    // 接收後續通道
    …
}
Go語言中提供了 select 關鍵字,可以同時響應多個通道的操作。select 的用法與 switch 語句非常類似,由 select 開始一個新的選擇塊,每個選擇條件由 case 語句來描述。

與 switch 語句可以選擇任何可使用相等比較的條件相比,select 有比較多的限制,其中最大的一條限制就是每個 case 語句裡必須是一個 IO 操作,大致結構如下:

select{
    case 操作1:
        響應操作1
    case 操作2:
        響應操作2
    …
    default:
        沒有操作情況
}

  • 操作1、操作2:包含通道收發語句,請參考下表。

    select 多路複用中可以接收的樣式
    操   作 語句範例
    接收任意資料 case <- ch;
    接收變數 case d :=  <- ch;
    傳送資料 case ch <- 100;

  • 響應操作1、響應操作2:當操作發生時,會執行對應 case 的響應操作。
  • default:當沒有任何操作時,預設執行 default 中的語句。

可以看出,select 不像 switch,後面並不帶判斷條件,而是直接去檢視 case 語句。每個 case 語句都必須是一個面向 channel 的操作。

基於此功能,我們可以實現一個有趣的程式:
ch := make(chan int, 1)
for {
    select {
        case ch <- 0:
        case ch <- 1:
    }
    i := <-ch
    fmt.Println("Value received:", i)
}
能看明白這段程式碼的含義嗎?其實很簡單,這個程式實現了一個隨機向 ch 中寫入一個 0 或者 1 的過程。當然,這是個死迴圈。關於 select 的詳細使用方法,請參考下節的範例。