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()
}
緩衝 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")
}
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 中的語句。
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)
}