通道 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