概述
原來分享基礎語法的時候,還未分享過 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
Gin 框架
基礎篇
本文歡迎轉發,轉發請注明作者和出處,謝謝!