[系列] Go - chan 通道
目錄
- 概述
- 宣告 chan
- 寫入 chan
- 讀取 chan
- 關閉 chan
- 示例
- 推薦閱讀
概述
原來分享基礎語法的時候,還未分享過 chan 通道,這次把它補上。
chan 可以理解為佇列,遵循先進先出的規則。
在說 chan 之前,咱們先說一下 go 關鍵字。
在 go 關鍵字後面加一個函式,就可以建立一個執行緒,函式可以為已經寫好的函式,也可以是匿名函式。
舉個例子:
func main() { fmt.Println("main start") go func() { fmt.Println("goroutine") }() fmt.Println("main end") }
輸出:
main start
main end
為什麼沒有輸出 goroutine ?
首先,我們清楚 Go 語言的執行緒是併發機制,不是並行機制。
那麼,什麼是併發,什麼是並行?
併發是不同的程式碼塊交替執行,也就是交替可以做不同的事情。
並行是不同的程式碼塊同時執行,也就是同時可以做不同的事情。
舉個生活化場景的例子:
你正在家看書,忽然電話來了,然後你接電話,通話完成後繼續看書,這就是併發,看書和接電話交替做。
如果電話來了,你一邊看書一遍接電話,這就是並行,看書和接電話一起做。
說回上面的例子,為什麼沒有輸出 goroutine ?
main 函式是一個主執行緒,是因為主執行緒執行太快了,子執行緒還沒來得及執行,所以看不到輸出。
現在讓主執行緒休眠 1 秒鐘,再試試。
func main() {
fmt.Println("main start")
go func() {
fmt.Println("goroutine")
}()
time.Sleep(1 * time.Second)
fmt.Println("main end")
}
輸出:
main start
goroutine
main end
這就對了。
接下來,看看如何使用 chan 。
宣告 chan
// 宣告不帶緩衝的通道 ch1 := make(chan string) // 宣告帶10個緩衝的通道 ch2 := make(chan string, 10) // 宣告只讀通道 ch3 := make(<-chan string) // 宣告只寫通道 ch4 := make(chan<- string)
注意:
不帶緩衝的通道,進和出都會阻塞。
帶緩衝的通道,進一次長度 +1,出一次長度 -1,如果長度等於緩衝長度時,再進就會阻塞。
寫入 chan
ch1 := make(chan string, 10)
ch1 <- "a"
讀取 chan
val, ok := <- ch1
// 或
val := <- ch1
關閉 chan
close(chan)
注意:
- close 以後不能再寫入,寫入會出現 panic
- 重複 close 會出現 panic
- 只讀的 chan 不能 close
- close 以後還可以讀取資料
示例
func main() {
fmt.Println("main start")
ch := make(chan string)
ch <- "a" // 入 chan
go func() {
val := <- ch // 出 chan
fmt.Println(val)
}()
fmt.Println("main end")
}
輸出:
main start
fatal error: all goroutines are asleep - deadlock!
What ? 這是為啥,剛開始就出師不利呀?
因為,定義的是一個無緩衝的 chan,賦值後就陷入了阻塞。
怎麼解決它?
宣告一個有緩衝的 chan。
func main() {
fmt.Println("main start")
ch := make(chan string, 1)
ch <- "a" // 入 chan
go func() {
val := <- ch // 出 chan
fmt.Println(val)
}()
fmt.Println("main end")
}
輸出:
main start
main end
為啥沒有輸出 a , 和前面一樣,主執行緒執行太快了,加個休眠 1 秒鐘,再試試。
func main() {
fmt.Println("main start")
ch := make(chan string, 1)
ch <- "a" // 入 chan
go func() {
val := <- ch // 出 chan
fmt.Println(val)
}()
time.Sleep(1 * time.Second)
fmt.Println("main end")
}
輸出:
main start
a
main end
這就對了。
再看一個例子:
func main() {
fmt.Println("main start")
ch := make(chan string)
go func() {
ch <- "a" // 入 chan
}()
go func() {
val := <- ch // 出 chan
fmt.Println(val)
}()
time.Sleep(1 * time.Second)
fmt.Println("main end")
}
輸出:
main start
a
main end
再看一個例子:
func producer(ch chan string) {
fmt.Println("producer start")
ch <- "a"
ch <- "b"
ch <- "c"
ch <- "d"
fmt.Println("producer end")
}
func main() {
fmt.Println("main start")
ch := make(chan string, 3)
go producer(ch)
time.Sleep(1 * time.Second)
fmt.Println("main end")
}
輸出:
main start
producer start
main end
帶緩衝的通道,如果長度等於緩衝長度時,再進就會阻塞。
再看一個例子:
func producer(ch chan string) {
fmt.Println("producer start")
ch <- "a"
ch <- "b"
ch <- "c"
ch <- "d"
fmt.Println("producer end")
}
func customer(ch chan string) {
for {
msg := <- ch
fmt.Println(msg)
}
}
func main() {
fmt.Println("main start")
ch := make(chan string, 3)
go producer(ch)
go customer(ch)
time.Sleep(1 * time.Second)
fmt.Println("main end")
}
輸出:
main start
producer start
producer end
a
b
c
d
main end
就到這吧。
推薦閱讀
gRPC
- Go gRPC Hello World
Gin 框架
- Gin 框架 - 自定義錯誤處理
- Gin 框架 - 資料繫結和驗證
- Gin 框架 - 使用 Logrus 日誌記錄
- Gin 框架 - 安裝和路由配置
基礎篇
- Go - chan 通道
- Go - 函式
- Go - 迴圈
- Go - Map 集合
- Go - Struct 結構體
- Go - Slice 切片
- Go - 陣列
- Go - 變數宣告
- Go - 環境安裝
本文歡迎轉發,轉發請註明作者和出處,謝