通道 Channel

Channel 可以想像成是一種資料結構,可以 push data 進去也可以 pull data 出來。

因為Channel會等待另一端完成 Push/Pull 的動作才會繼續往下處理,
而且特性使其可以在Goroutines間同步處理的資料,而不用使用 lock、unlock 等方式。

Create Channel


    ch := make(chan int)

Push data to Channel


    ch <- d 

Pull data from Channel


    d := <- ch

Channel是用來讓Goroutine溝通時使用的一種資料結構,並且由於其阻塞的特性,它也能夠當成一種等待goroutine的方法。


    package main

    import (
        "fmt"
        "time"
    )

    func main() {
        ch := make(chan string)

        go calculator(ch)

        time.Sleep(3 * time.Second)
        fmt.Println(<-ch)
        time.Sleep(time.Second)
        fmt.Println("main goroutine finished")
    }

    func work(ch chan string) {
        fmt.Println("Start to work goroutines")
        time.Sleep(time.Second)
        fmt.Println("Stop to work goroutines")

        ch <- "FINISH"

        fmt.Println("Finish work")
    }

    # Result:
    # Start to work goroutines
    # Stop to work goroutines
    # FINISH
    # Finish work
    # Main goroutine finished

這邊的三秒延遲目的是為了讓 main thread 慢於 goroutines,一秒延遲則是模擬 goroutines 的作業時間!
所以他會依序的去進行 goroutines 作業→打印 channel 的內容→完成 goroutines function →完成 main thread。

通道分為兩種,有 buffer 與無 buffer 的,也就是有儲存空間限制的 channel 與無限制的 channel。

Unbuffered channel


    package main

    import (
        "fmt"
	    "time"
    )

    func main() {
	    ch := make(chan int)
	    go f(ch)
	    got := <-ch
	    fmt.Println(got)
    }

    func f(ch chan int) {
	    time.Sleep(time.Second * 2)
	    ch <- 999
    }
        
    # Result:
    # 999

由上面例子我們可以得知,Unbuffered Channel 有一個特性:

Push 一筆資料會造成 Pusher 的等待
Pull 時沒有資料則會造成 Puller 的等待
也因此如果 Pusher 的執行一次時間較 Puller 短,會造成 Pusher 被迫等待 Puller 拉取完後,才能進行下一次的 Push,而這樣的等待是相當浪費時間的。

而 Buffered Channel 正好解決了這種問題。

Buffered channel

差別僅在於 Variable := make(chan Type, Number)

Deadlock


    package main

    func main() {
	    ch := make(chan int, 2)
	    go func3(ch)
	    ch  <- 100
	    ch  <- 99

	    ch  <- 98 // 發生deadlock
    }

    func func3(ch chan int) {
    }

    # Result:
    # fatal error: all goroutines are asleep - deadlock!

有限制儲存空間的通道,若限制放兩個,就只能有兩筆數據,倘若塞入第三筆的話則會造成死結 DeadLock

Block vs Deadlock

以上例來說,通常 chan 塞不下第三筆數據時,只會 發生 Block,而當 Block 永遠無法解開的情況發生,則是 Deadlock。
只要通道 Channel 塞不下,或者沒東西可抓,都會發生 Block。

Block


    package main

    import (
        "fmt"
	    "time"
    )

    func main() {
	    ch := make(chan int, 2)
	    go func1(ch)
	    for i := 0; i  < 10; i++ {
		    ch  <- i
		    fmt.Println("main sent", i)
	    }
	    time.Sleep(time.Second)
    }

    func func1(ch chan int) {
	    for {
		    i :=  <-ch
		    fmt.Println("func1 got", i)
		    time.Sleep(time.Millisecond * 100)
	    }
    }

    # Result:
    # main sent 0
    # main sent 1
    # main sent 2
    # func1 got 0
    # func1 got 1
    # main sent 3
    # func1 got 2
    # main sent 4
    # func1 got 3
    # main sent 5
    # func1 got 4
    # main sent 6
    # func1 got 5
    # main sent 7
    # func1 got 6
    # main sent 8
    # func1 got 7
    # main sent 9
    # func1 got 8
    # func1 got 9

主程式塞2個數字後會 Block,必須由 func1 每0.1秒把數字依序讀出,主程式才可以繼續將數字塞入。
雖然慢,但程式不會 Block,只是會造成執行效率低下而已。
把Buffer Size: 2換成5


    package main

    import (
        "fmt"
	    "time"
    )

    func main() {
	    ch := make(chan int, 5)
	    go func1(ch)
	    for i := 0; i  < 10; i++ {
		    ch  <- i
		    fmt.Println("main sent", i)
	    }
	    time.Sleep(time.Second)
    }

    func func1(ch chan int) {
	    for {
		    i :=  <-ch
		    fmt.Println("func1 got", i)
		    time.Sleep(time.Millisecond * 100)
	    }
    }

    # Result:
    # main sent 0
    # main sent 1
    # main sent 2
    # main sent 3
    # main sent 4
    # main sent 5
    # func1 got 0
    # func1 got 1
    # main sent 6
    # func1 got 2
    # main sent 7
    # func1 got 3
    # main sent 8
    # func1 got 4
    # main sent 9
    # func1 got 5
    # func1 got 6
    # func1 got 7
    # func1 got 8
    # func1 got 9
An unhandled error has occurred. Reload 🗙