go channel 詳細介紹

一、介紹

go 以高併發著稱,而支撐這個高併發的,goroutine 協程是主要原因之一。
goroutine 讓我們輕鬆實現異步,非阻塞。
但這種異步,也帶來的問題是執行順序的不確定性。
以及數據同步的問題。在這種情況下,go 官方引入了 channel 來通信,實現數據共享。

channel是用於goroutine的數據通信


在這張圖上面,我們在主協程,總共開了 5 個 goroutine,然後每個 goroutine,通過 ch 的 channel 通信共享內存,處理寫以及讀的數據。

1.1 語法

Channel 通過操作符 <- 執行 寫入以及讀取操作。

ch <- v    // 寫入v到channel
v := <-ch  // 從channel讀取數據。
(箭頭的指向就是數據的流向)

1.2 操作

通道有發送(send)、接收 (receive)和關閉(close)三種操作。
我們先定義一個 channel,因爲 channel 的空值是 nil,所以必須 make 才能使用。

ch := make(chan int)

1.2.1 發送

將一個值發送到通道中。

ch <- 10 //把10發送到 ch 中

1.2.2 接收

從一個通道中接收值。

x := <-ch //從ch中接收值,並賦值給x
<-ch  //從ch中接收值,忽略結果

1.2.3 close()

我們通過調用內置的 close 函數來關閉通道。

close(ch)

1.3 channel 在 nil 的時候 panic 報錯,使用的時候要注意

1.3.1 關閉一個 nil 值 channel 會引發 panic

//main.關閉一個 nil 值 channel 會引發 panic。!
func TestNew(t *testing.T) {
    var ch chan struct{}
    close(ch)
}

1.3.2 關閉一個已關閉的 channel 會引發 panic

//關閉一個已關閉的 channel 會引發 panic
func TestClose(t *testing.T) {
    ch := make(chan struct{})
    close(ch)
    close(ch)
}

1.3.3 向一個已關閉的 channel 發送數據。會引發 panic

//向一個已關閉的 channel 發送數據。會引發 panic。
func TestCloseSend(t *testing.T) {
    ch := make(chan struct{})
    close(ch)
    ch <- struct{}{}
}

二、channel 用來同步

我們讀取一個 chanel,這個 channel 一直到有寫入的時候,才能讀取,否則會一直阻塞着。

func TestSync(t *testing.T) {
    done := make(chan bool)
    go worker(done)
    //一直阻塞着,等待任務完成
    <-done
}

func worker(done chan bool) {
    time.Sleep(time.Second*10)
    // 通知任務已完成
    done <- true
}

這樣也有一個問題,萬一 worker 這個協程,有很長時間的話,就會一直阻塞,我們可以加一個保底的時間。

func TestSync(t *testing.T) {
    done := make(chan bool)
    go worker(done)
    //一直阻塞着,等待任務完成
    select {
    case <-done:
    case <-time.After(time.Second):
    }
}
func worker(done chan bool) {
    time.Sleep(time.Second * 10)
    // 通知任務已完成
    done <- true
}

我們用 select 改進了一下,讓它有一個 1 秒的打底。超過 1 秒,就可以走出來了,不用一直阻塞着。

三、多個協程,多個 channel 用 select 來監聽

如下面的代碼,
開了三個 channel(http1,http2,time.After),兩個子協程。
用 select 來監聽,哪個 channel 先返回。

import (
    "math/rand"
    "testing"
    "time"
)

func TestSelect(t *testing.T) {
    res1 := make(chan string)
    res2 := make(chan string)
    go http1(res1)
    go http2(res2)

    select {
    case v1 := <-res1:
        t.Log(v1)
    case v2 := <-res2:
        t.Log(v2)
    case <-time.After(650 * time.Millisecond):
        t.Log("650  Millisecond over, timeover")
    }
    time.Sleep(time.Second * 1)
}

func http1(res1 chan string) {
    time.Sleep(500 * time.Millisecond)
    res1 <- "res1 ok"
}

func http2(res3 chan string) {
    //[0-1000)Millisecond 隨機
    rand.Seed(time.Now().UnixNano())
    time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
    res3 <- "res3 ok"
}

輸出:

res2 ok

或者

res1 ok

看 http2 隨機的事多少。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/7yJ9Oulv40yKEJPQj5vflg