Golang學習筆記:channel

channel

channel是goroutine之間的通訊機制,它可讓一個goroutine經過它給另外一個goroutine發送數據,每一個channel在建立的時候必須指定一個類型,指定的類型是任意的。
使用內置的make函數,能夠建立一個channel類型:json

ch := make(chan int)

發送和接受

channel主要的操做有發送和接受:函數

// 發送數據到channel
ch <- 1
// 從channel接受數據
x := <- ch

如向channel發送數據的時候,該goroutine會一直阻塞直到另外一個goroutine接受該channel的數據,反之亦然,goroutine接受channel的數據的時候也會一直阻塞直到另外一個goroutine向該channel發送數據,以下面操做:code

func main() {
    ch := make(chan string)
    // 在此處阻塞,而後程序會彈出死鎖的報錯
    c <- "hello"
    fmt.Println("channel has send data")
}

正確的操做:blog

func main() {
    ch := make(chan string)
    go func(){
        // 在執行到這一步的時候main goroutine纔會中止阻塞
        str := <- ch
        fmt.Println("receive data:" + str)
    }()
    ch <- "hello"
    fmt.Println("channel has send data")
}

說到channel的阻塞,就不得不說到有緩衝的channel。隊列

帶緩衝的channel

帶緩衝的channel的建立和不帶緩衝的channel(也就是上面用的channel)的建立差很少,只是在make函數的第二個參數指定緩衝的大小。string

// 建立一個容量爲10的channel
ch := make(chan int, 10)

帶緩衝的channel就像一個隊列,聽從先進先從的原則,發送數據向隊列尾部添加數據,從頭部接受數據。
image產品

goroutine向channel發送數據的時候若是緩衝還沒滿,那麼該goroutine就不會阻塞。it

ch := make(chan int, 2)
// 前面兩次發送數據不會阻塞,由於緩衝還沒滿
ch <- 1
ch <- 2
// goroutine會在這裏阻塞
ch <- 3

反之若是接受該channel數據的時候,若是緩衝有數據,那麼該goroutine就不會阻塞。for循環

channel與goroutine之間的應用能夠想象成某個工廠的流水線工做,流水線上面有打磨,上色兩個步驟(兩個goroutine),負責打磨的工人生產完成後會傳給負責上色的工人,上色的生產依賴於打磨,兩個步驟之間的可能存在存放槽(channel),若是存放槽存滿了,打磨工人就不能繼續向存放槽當中存放產品,直到上色工人拿走產品,反之上色工人若是把存放槽中的產品都上色完畢,那麼他就只能等待新的產品投放到存放槽中。event

備註

其實在實際應用中,帶緩衝的channel用的並很少,繼續拿剛纔的流水線來作案例,若是打磨工人生產速度比上色工人工做速度要快,那麼即使再多容量的channel,也會早晚被填滿而後打磨工人會被阻塞,反之若是上色工人生產速度大於打磨工人速度,那麼有緩衝的channel也是一直處於沒有數據,上色工人很容易長時間處於阻塞的狀態。

所以比較好的解決方法仍是針對生產速度較慢的一方多加人手,也就是多開幾個goroutine來進行處理,有緩衝的channel最好用處只是拿來防止goroutine的完成時間有必定的波動,須要把結果緩衝起來,以平衡總體channel通訊。

單方向的channel

使用channel來使不一樣的goroutine去進行通訊,不少時候都和消費者生產者模式很類似,一個goroutine生產的結果都用channel傳送給另外一個goroutine,一個goroutine的執行依賴與另外一個goroutine的結果。
所以不少狀況下,channel都是單方向的,在go裏面能夠把一個無方向的channel轉換爲只接受或者只發送的channel,可是卻不能反過來把接受或發送的channel轉換爲無方向的channel,適當地把channel改爲單方向,能夠達到程序強約束的作法,相似於下面例子:

fuc main(){
    ch := make(ch chan string)
    
    go func(out chan<- string){
        out <- "hello"
    }(ch)
    
    go func(in <-chan string){
        fmt.Println(in)
    }(ch)
    
    time.Sleep(2 * time.Second)
}

select多路複用

在一個goroutine裏面,對channel的操做極可能致使咱們當前的goroutine阻塞,而咱們以後的操做都進行不了。而若是咱們又須要在當前channel阻塞進行其餘操做,如操做其餘channel或直接跳過阻塞,能夠經過select來達到多個channel(可同時接受和發送)複用。以下面咱們的程序須要同時監聽多個頻道的信息:

broadcaster1 := make(chan string) // 頻道1
broadcaster2 := make(chan string) // 頻道2
select {
    case mess1 := <-broadcaster1:
        fmt.Println("來自頻道1的消息:" + mess1)
    case mess2 := <-broadcaster2:
        fmt.Println("來自頻道2的消息:" + mess2)
    default:
        fmt.Println("暫時沒有任何頻道的消息,請稍後再來~")
        time.Sleep(2 * time.Second)
}

select和switch語句有點類似,找到匹配的case執行對應的語句塊,可是若是有兩個或以上匹配的case語句,那麼則會隨機選擇一個執行,若是都不匹配就會執行default語句塊(若是含有default的部分的話)。
值得注意的是,select通常配合for循環來達到不斷輪詢管道的效果,可能不少小夥伴想着寫個在某個case裏用break來跳出for循環,這是不行的,由於break只會退出當前case,須要使用return來跳出函數或者弄個標誌位標記退出

var flag = 0
for {
    if flag == 1 {break}
    select {
        case message := <- user.RecMess :
            event := gjson.Get(string(message), "event").String()
            if event == "login" {
                Login(message, user)
            }
            break
        case <- user.End :
            flag = 1
            break
    }
}

關閉

channel能夠接受和發送數據,也能夠被關閉。

close(ch)

關閉channel後,全部向channel發送數據的操做都會引發panic,而被close以後的channel仍然能夠接受以前已經發送成功的channel數據,若是數據所有接受完畢,那麼再從channel裏面接受數據只會接收到零值得數據。

channel的關閉能夠用來操做其餘goroutine退出,在運行機制方面,goroutine只有在自身所在函數運行完畢,或者主函數運行完畢纔會打斷,因此咱們能夠利用channel的關閉做爲程序運行入口的一個標誌位,若是channel關閉則中止運行。

沒法直接讓一個goroutine直接中止另外一個goroutine,但可使用通訊的方法讓一個goroutine中止另外一個goroutine,以下例子就是程序一邊運行,一邊監聽用戶的輸入,若是用戶回車,則退出程序。

func main() {
    shutdown := make(chan struct{})
    var n sync.WaitGroup
    n.Add(1)
    go Running(shutdown, &n) 
    n.Add(1)
    go ListenStop(shutdown, &n) 
    n.Wait()
}

func Running(shutdown <-chan struct{}, n *sync.WaitGroup) {
    defer n.Done()
    for {
        select {
        case <-shutdown:
            // 一旦關閉channel,則能夠接收到nil。
            fmt.Println("shutdown goroutine")
            return
        default:
            fmt.Println("I am running")
            time.Sleep(1 * time.Second)
        }   
    }   
}

func ListenStop(shutdown chan<- struct{}, n *sync.WaitGroup) {
    defer n.Done()
    os.Stdin.Read(make([]byte, 1)) 
    // 若是用戶輸入了回車則退出關閉channel
    close(shutdown)
}

利用channel關閉時候的傳送的零值信號能夠有效地退出其餘goroutine,特別是關閉多個goroutine的時候,就不須要向channel傳輸多個信息了。

相關文章
相關標籤/搜索