一篇文章帶你瞭解 Go 語言基礎之併發(channel)

前言

Hi,大家好,我是碼農,星期八,本篇繼續帶來 Go 語言併發基礎,channel 如何使用。

看看 Go 協程如何配合 channel。

快來上車叭。

爲什麼需要 channel

channel 在 Go 中,也叫做管道,是用來多線程之間共享數據的。

通常情況下,在 Go 中共享數據用的也是channel,但是在 Go 有兩種共享數據方式。

爲啥子共享內存通訊不太推薦?

示例代碼: 多線程修改一個值。

函數

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 是先入先出結構,就像這樣。

注意事項:

無緩衝管道

無緩衝就是這個管道沒有長度,就像這樣。

就像快讀員沒有快遞櫃,需要直接將快遞給客戶,如果沒人要就撂攤子。

示例代碼

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 中完成了發送值和取值兩個操作,所以會出現上述問題,但是結果是沒有錯的。

單向通道

我們知道通道是可以發送值取值的,但是某些場景爲了安全起見,理論來說只能取值,後者只能發送值。

單向通道通常只在函數參數中體現。

修改上述快遞員代碼。

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 如何配合,最後用單向通道又加固了一下代碼。

我的代碼中使用了中文命名變量名是爲了好看,實際開發中千萬不要這樣!!!

上述代碼一定要敲一下,如果在操作過程中有任何問題,記得下面留言,我們看到會第一時間解決問題。

不積跬步無以至千里,不積小流無以成江海,給自己一個成長的時間

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/nIaJw3yU-pXVC2ah3t0w6Q