跳至主要内容

Channel(通道)

https://www.geeksforgeeks.org/buffered-channel-in-golang/

介紹

Goroutine 之間可以使用 channel 做通訊,以下特性:

  • 向關閉的 channel 寫數據會造成 panic
  • 關閉的 channel 可以讀數據
  • 對 nil channel 讀寫數據都會阻塞

使用方式

make(chan Type)
make(chan Type, capacity)
channel1<-value         // 發送value到channel1
<-channel // 接收但丟棄
x := <-channel1 // 接受數據付值 x
x, ok := <-channel1 // 接受數據付值 x,同時檢查通道是否關閉或者為空
close (channel1) // 關閉通道 channel1

無緩衝 channel

特性是在主程式內,通道寫入數據,會阻塞直到通道數據被讀取,才能繼續寫入。

package main

import (
"fmt"
"time"
)

func main() {
fmt.Println("Hello Go")

c := make(chan int)

go func() {
defer fmt.Println("goroutine end...")
fmt.Println("goroutine start")

for i := 0; i < 5; i++ {
fmt.Println("goroutine start, send val=", i, "len(c)=", len(c), "cap(c)=", cap(c))
c <- i
}

// 關閉通道
close(c)
}()

time.Sleep(2 * time.Second)

for i := range c {
fmt.Println("main goroutine start: ", i)
}

fmt.Println("main goroutine end")
}

程式出現 pannic,channel 寫入資料,但沒有讀取 channel。

package main

import (
"fmt"
)

func main() {
c := make(chan int)
c <- 10
fmt.Println(<-c)
}

讀/寫 channel 要在不同協程才可正常運作。

package main

import (
"fmt"
)

func main() {
c := make(chan int)
go func() {
c <- 10
}()
fmt.Println(<-c)
}

main 協程寫 channel 已經結束,才運行另一個協程做讀取 channel,引起 pannic。

package main

import (
"fmt"
)

func main() {
c := make(chan int)
c <- 10
go func() {
fmt.Println(<-c)
}()
}

使用 sync.waitGroup 來等待協程。

package main

import (
"fmt"
"sync"
)

func main() {
c := make(chan int)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
c <- 10
}()
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(<-c)
}()
wg.Wait()
}

Cache Flow

緩衝 channel

在 make 時加入第二個參數即為 Buffered Channel,第二個參數代表 buffered channel 的容量。 通道寫入數據達到 buffer 容量時,寫入通道阻塞,直到通道讀數據,才可以繼續寫入數據。

package main

import (
"fmt"
"time"
)

func main() {
fmt.Println("Hello Go")

c := make(chan int, 3)

go func() {
defer fmt.Println("goroutine end...")
fmt.Println("goroutine start")

for i := 0; i < 5; i++ {
fmt.Println("goroutine start, send val=", i, "len(c)=", len(c), "cap(c)=", cap(c))
c <- i
}

// 關閉通道
close(c)
}()

time.Sleep(2 * time.Second)

// ok 變數表示通道是否關閉
if v, ok := <-c; ok {
fmt.Println(v)
}

fmt.Println("main goroutine end")
}

Cache Flow

Channel & Select(多路復用)

select 是操作系统中的系統調用,我們經常會使用 select、poll 和 epoll 等函數構建 I/O 多路復用模型提昇程序的性能。Go 語言的 select 與操作系統中中的 select 比較相似。這些 case 中的表達式必須都是 Channel 的收發操作,會遇到以下狀況:

  • select 能在 Channel 上進行非阻塞的收發操作。
  • select 遇到多個 Channel 同時響應時,會隨機執行其中一種 case。

在一般情況下,select 語句會阻塞當前 Goroutine 並等待多個 Channel 中的一個達到可以收發的狀態。但是如果 select 控制結構中包含 default 語句,那麼這個 select 語句在執行時會遇到以下兩種情況:

  • 當存在可以收發的 Channel 時,直接處理該 Channel 對應的 case。
  • 當不存在可以收發的 Channel 時,執行 default 中的語句。

Cache Flow

package main

import (
"fmt"
"time"
)


func fibonacii(c, quit chan int) {

x, y := 1, 1

for {
select {
case c <- x:
// c 可寫,則該case執行
x = y
y = x + y
case <-quit:
fmt.Println("quit")
return
}
}

}

func main() {
c := make(chan int)
quit := make(chan int)

go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}

quit <- 0
}()

fibonacii(c, quit)
}