一篇文章帶你了解Go語言基礎之并發(channel)
前言
Hi,大家好,我是碼農,星期八,本篇繼續帶來Go語言并發基礎,channel如何使用。
看看Go協程如何配合channel。
為什么需要channel
channel在Go中,也叫做管道,是用來多線程之間共享數據的。
通常情況下,在Go中共享數據用的也是channel,但是在Go有兩種共享數據方式。
- 共享內存實現通訊。
- 通過管道(channel)通訊(推薦)。
為啥子共享內存通訊不太推薦?
示例代碼:多線程修改一個值。
函數
- func Calc() {
- defer wg.Done()
- NUM = NUM - 1
- }
main
- var NUM = 100
- var wg sync.WaitGroup
- func main() {
- for i := 0; i<100;i++ {
- wg.Add(1)
- go Calc()
- }
- wg.Wait()
- fmt.Println(NUM)
- }
執行結果

沒錯,是2,懵了吧,哈哈哈,理論應該是0才對呀。
這是為啥?
這就是共享內存不太推薦的原因,我們的代碼已經是多線程了。
在第一個函數代碼中,第3行,NUM = NUM - 1。
如果多個線程同時執行到這一行,并且沒有加鎖,就會出現數據錯亂。
那該怎么做呢?
加鎖,加鎖可以保證某一段代碼只能被一個線程執行,防止被爭搶。
代碼
- func Calc() {
- defer wg.Done()
- mutex.Lock()
- NUM = NUM - 1
- mutex.Unlock()
- }
第3行加鎖,第5行解鎖。
執行結果

這次真的是0的,不管執行幾次。
但是會發現一個問題,如果采用這種方式,需要常常注意競爭問題。
所以不是太推薦,需要考慮的比較多,并且各種加鎖會消耗性能。
channel語法
channel格式
- var 變量名 chan 類型
- 例如
- var x1 chan int //x1管道里面只能存int類型數據
- var x2 chan string //x2管道里面只能存字符串類型數據
注意

定義管道時,chan int是一個整體,別搞錯了各位。
創建channel
創建channel,只能通過make創建。
格式
- var 變量名 = make(chan 類型,[管道大小])
- 示例
- var chan1 = make(chan int,10)//管道可以放10個int元素
- var chan2 = make(chan string,5)//管道可以放5個string元素
channel操作
創建一個管道。
- ch = make(chan int,10)
channel是一個管道,就像一個管子。
所以可以像管子里面塞東西,并且能取東西,關閉管道就是這個管道不能用了,里面的值取完就打樣了。
像管子塞東西(發送)ch <- 666。
從管子取東西(接收)var x = <- ch。
關閉管子close(ch)。
注意:channel是先入先出結構,就像這樣。

注意事項:
- 如果通道塞滿了,再塞 會阻塞住。
- 如果通道關閉了,是不能再塞值了,否則會panic。
- 即使通道關閉了,依然可以取值,直到將管道的值取完,取完后得到的是對應類型零值。
- 管道不能重復關閉,重復關閉會panic。
無緩沖管道
無緩沖就是這個管道沒有長度,就像這樣。
就像快讀員沒有快遞柜,需要直接將快遞給客戶,如果沒人要就撂攤子。

示例代碼
- package main
- import (
- "fmt"
- )
- //模擬張三
- func 張三(x chan string) {
- var a = <-x
- fmt.Println(a)
- }
- func main() {
- //通道沒有長度,就是無緩沖通道
- var x = make(chan string)
- go 張三(x)
- x <- "張三的快遞"
- fmt.Println("張三快遞交付成功")
- }
第16行寫入一個值,同理,張三就要等著去接,如果沒人接,那就完了。
假設注釋第9行代碼。
直接報錯,all goroutines are asleep - deadlock!,這句話的意思是所有的協程都睡著了,死鎖
無緩沖說明通道長度為0,發送一個值會阻塞住。
這就相當于快遞員直接找張三,但是張三沒了,但是快遞員還得一直等著,等等等,然后掛了,終究還是沒送出去。
有緩沖管道

這個就簡單啦,多了一個快遞柜,快遞員直接將快遞仍快遞柜就行了。
示例代碼
- package main
- import (
- "fmt"
- "sync"
- )
- var wg sync.WaitGroup
- //快遞員,快遞員放10個快遞
- func 快遞員(kuaidigui chan string) {
- defer wg.Done()
- for i := 0; i < 10; i++ {
- fmt.Println("快遞員放入了第",i,"快遞")
- kuaidigui <- fmt.Sprintf("第%d個快遞", i)
- }
- //放完快遞就關閉了通道
- close(kuaidigui)
- }
- //張三,拿走3個快遞
- func 張三(kuaidigui chan string) {
- defer wg.Done()
- for i := 0; i < 3; i++ {
- fmt.Println("張三拿走" + <-kuaidigui)
- }
- }
- //李四拿走7個快遞
- func 李四(kuaidigui chan string) {
- defer wg.Done()
- for i := 0; i < 7; i++ {
- fmt.Println("李四拿走" + <-kuaidigui)
- }
- }
- func main() {
- //快遞柜,10個大小
- var 快遞柜 = make(chan string, 10)
- wg.Add(3)
- go 快遞員(快遞柜)
- go 張三(快遞柜)
- go 李四(快遞柜)
- wg.Wait()
- }
執行結果

遍歷channel兩種方式
代碼
- func main() {
- //快遞柜,10個大小
- var ch = make(chan int, 10)
- //向管道中發送值
- for i := 0; i < 10; i++ {
- ch <- i
- }
- //方式一取值
- //for {
- //i, ok := <-ch
- ////取完值ok就是false
- //if !ok {
- // //結束循環
- // break
- //}
- //fmt.Println(i)
- //}
- //方式二取值
- for i:=range ch{
- fmt.Println(i)
- }
- }
執行結果

報錯是因為我在main中完成了發送值和取值兩個操作,所以會出現上述問題,但是結果是沒有錯的。
單向通道
我們知道通道是可以發送值和取值的,但是某些場景為了安全起見,理論來說只能取值,后者只能發送值。
單向通道通常只在函數參數中體現。
- 形參 chan<- chan類型只寫。
- 形參 <-chan chan類型只讀。
修改上述快遞員代碼。
- package main
- import (
- "fmt"
- "sync"
- )
- var wg sync.WaitGroup
- //快遞員,快遞員放10個快遞,只寫 chan<- string
- func 快遞員(kuaidigui chan<- string) {
- defer wg.Done()
- for i := 0; i < 10; i++ {
- fmt.Println("快遞員放入了第", i, "快遞")
- kuaidigui <- fmt.Sprintf("第%d個快遞", i)
- }
- //放完快遞就關閉了通道
- close(kuaidigui)
- }
- //張三,拿走3個快遞,只讀<-chan string
- func 張三(kuaidigui <-chan string) {
- defer wg.Done()
- for i := 0; i < 3; i++ {
- fmt.Println("張三拿走" + <-kuaidigui)
- }
- }
- //李四拿走7個快遞
- func 李四(kuaidigui <-chan string) {
- defer wg.Done()
- for i := 0; i < 7; i++ {
- fmt.Println("李四拿走" + <-kuaidigui)
- }
- }
- func main() {
- //快遞柜,10個大小
- var 快遞柜 = make(chan string, 10)
- wg.Add(3)
- go 快遞員(快遞柜)
- go 張三(快遞柜)
- go 李四(快遞柜)
- wg.Wait()
- }
總結
上述講述了Go語言并發如何和channel配合使用,畢竟我們一般的任務都不是單獨運行的,都是互相配合的。
我們講述了如何創建channel,如何使用channel,有緩沖管道和無緩沖管道區別,并且拒了一個快遞員例子來展示協程和channel如何配合,最后用單向通道又加固了一下代碼。
我的代碼中使用了中文命名變量名是為了好看,實際開發中千萬不要這樣!!!
上述代碼一定要敲一下,如果在操作過程中有任何問題,記得下面留言,我們看到會第一時間解決問題。
不積跬步無以至千里,不積小流無以成江海,給自己一個成長的時間