[系列] Go - 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

Gin 框架

基礎篇

本文歡迎轉發,轉發請注明作者和出處,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM