Hi,大家好,我是明哥,
在自己學習 Golang 的這段時間里,我寫了詳細的學習筆記放在我的個人微信公眾號 《Go編程時光》,對于 Go 語言,我也算是個初學者,因此寫的東西應該會比較適合剛接觸的同學,如果你也是剛學習 Go 語言,不防關注一下,一起學習,一起成長,
我的在線博客:http://golang.iswbm.com
我的 Github:github.com/iswbm/GolangCodingTime
Go 語言之所以開始流行起來,很大一部分原因是因為它自帶的并發機制,
如果說 goroutine 是 Go語言程式的并發體的話,那么 channel(信道) 就是 它們之間的通信機制,channel,是一個可以讓一個 goroutine 與另一個 goroutine 傳輸資訊的通道,我把他叫做信道,也有人將其翻譯成通道,二者都是一個概念,
信道,就是一個管道,連接多個goroutine程式 ,它是一種佇列式的資料結構,遵循先入先出的規則,
1. 信道的定義與使用
每個信道都只能傳遞一種資料型別的資料,所以在你宣告的時候,你得指定資料型別(string int 等等)
var 信道實體 chan 信道型別
// 定義容量為10的信道
var 信道實體 [10]chan 信道型別
宣告后的信道,其零值是nil,無法直接使用,必須配合make函進行初始化,
信道實體 = make(chan 信道型別)
亦或者,上面兩行可以合并成一句,以下我都使用這樣的方式進行信道的宣告
信道實體 := make(chan 信道型別)
假如我要創建一個可以傳輸int型別的信道,可以這樣子寫,
// 定義信道
pipline := make(chan int)
信道的資料操作,無非就兩種:發送資料與讀取資料
// 往信道中發送資料
pipline<- 200
// 從信道中取出資料,并賦值給mydata
mydata := <-pipline
信道用完了,可以對其進行關閉,避免有人一直在等待,但是你關閉信道后,接收方仍然可以從信道中取到資料,只是接收到的會永遠是 0,
close(pipline)
對一個已關閉的信道再關閉,是會報錯的,所以我們還要學會,如何判斷一個信道是否被關閉?
當從信道中讀取資料時,可以有多個回傳值,其中第二個可以表示 信道是否被關閉,如果已經被關閉,ok 為 false,若還沒被關閉,ok 為true,
x, ok := <-pipline
2. 信道的容量與長度
一般創建信道都是使用 make 函式,make 函式接收兩個引數
- 第一個引數:必填,指定信道型別
- 第二個引數:選填,不填默認為0,指定信道的容量(可快取多少資料)
對于信道的容量,很重要,這里要多說幾點:
- 當容量為0時,說明信道中不能存放資料,在發送資料時,必須要求立馬有人接收,否則會報錯,此時的信道稱之為無緩沖信道,
- 當容量為1時,說明信道只能快取一個資料,若信道中已有一個資料,此時再往里發送資料,會造成程式阻塞, 利用這點可以利用信道來做鎖,
- 當容量大于1時,信道中可以存放多個資料,可以用于多個協程之間的通信管道,共享資源,
至此我們知道,信道就是一個容器,
若將它比做一個紙箱子
- 它可以裝10本書,代表其容量為10
- 當前只裝了1本書,代表其當前長度為1
信道的容量,可以使用 cap 函式獲取 ,而信道的長度,可以使用 len 長度獲取,
package main
import "fmt"
func main() {
pipline := make(chan int, 10)
fmt.Printf("信道可緩沖 %d 個資料\n", cap(pipline))
pipline<- 1
fmt.Printf("信道中當前有 %d 個資料", len(pipline))
}
輸出如下
信道可緩沖 10 個資料
信道中當前有 1 個資料
3. 緩沖信道與無緩沖信道
按照是否可緩沖資料可分為:緩沖信道 與 無緩沖信道
緩沖信道
允許信道里存盤一個或多個資料,這意味著,設定了緩沖區后,發送端和接收端可以處于異步的狀態,
pipline := make(chan int, 10)
無緩沖信道
在信道里無法存盤資料,這意味著,接收端必須先于發送端準備好,以確保你發送完資料后,有人立馬接收資料,否則發送端就會造成阻塞,原因很簡單,信道中無法存盤資料,也就是說發送端和接收端是同步運行的,
pipline := make(chan int)
// 或者
pipline := make(chan int, 0)
4. 雙向信道與單向信道
通常情況下,我們定義的信道都是雙向通道,可發送資料,也可以接收資料,
但有時候,我們希望對信道的資料流向做一些控制,比如這個信道只能接收資料或者這個信道只能發送資料,
因此,就有了 雙向信道 和 單向信道 兩種分類,
雙向信道
默認情況下你定義的信道都是雙向的,比如下面代碼
import (
"fmt"
"time"
)
func main() {
pipline := make(chan int)
go func() {
fmt.Println("準備發送資料: 100")
pipline <- 100
}()
go func() {
num := <-pipline
fmt.Printf("接收到的資料是: %d", num)
}()
// 主函式sleep,使得上面兩個goroutine有機會執行
time.Sleep(1)
}
單向信道
單向信道,可以細分為 只讀信道 和 只寫信道,
定義只讀信道
var pipline = make(chan int)
type Receiver = <-chan int // 關鍵代碼:定義別名型別
var receiver Receiver = pipline
定義只寫信道
var pipline = make(chan int)
type Sender = chan<- int // 關鍵代碼:定義別名型別
var sender Sender = pipline
仔細觀察,區別在于 <- 符號在關鍵字 chan 的左邊還是右邊,
<-chan表示這個信道,只能從里發出資料,對于程式來說就是只讀chan<-表示這個信道,只能從外面接收資料,對于程式來說就是只寫
有同學可能會問:為什么還要先宣告一個雙向信道,再定義單向通道呢?比如這樣寫
type Sender = chan<- int
sender := make(Sender)
代碼是沒問題,但是你要明白信道的意義是什么?(以下是我個人見解
信道本身就是為了傳輸資料而存在的,如果只有接收者或者只有發送者,那信道就變成了只入不出或者只出不入了嗎,沒什么用,所以只讀信道和只寫信道,唇亡齒寒,缺一不可,
當然了,若你往一個只讀信道中寫入資料 ,或者從一個只寫信道中讀取資料 ,都會出錯,
完整的示例代碼如下,供你參考:
import (
"fmt"
"time"
)
//定義只寫信道型別
type Sender = chan<- int
//定義只讀信道型別
type Receiver = <-chan int
func main() {
var pipline = make(chan int)
go func() {
var sender Sender = pipline
fmt.Println("準備發送資料: 100")
sender <- 100
}()
go func() {
var receiver Receiver = pipline
num := <-receiver
fmt.Printf("接收到的資料是: %d", num)
}()
// 主函式sleep,使得上面兩個goroutine有機會執行
time.Sleep(1)
}
5. 遍歷信道
遍歷信道,可以使用 for 搭配 range關鍵字,在range時,要確保信道是處于關閉狀態,否則回圈會阻塞,
import "fmt"
func fibonacci(mychan chan int) {
n := cap(mychan)
x, y := 1, 1
for i := 0; i < n; i++ {
mychan <- x
x, y = y, x+y
}
// 記得 close 信道
// 不然主函式中遍歷完并不會結束,而是會阻塞,
close(mychan)
}
func main() {
pipline := make(chan int, 10)
go fibonacci(pipline)
for k := range pipline {
fmt.Println(k)
}
}
6. 用信道來做鎖
當信道里的資料量已經達到設定的容量時,此時再往里發送資料會阻塞整個程式,
利用這個特性,可以用當他來當程式的鎖,
示例如下,詳情可以看注釋
package main
import {
"fmt"
"time"
}
// 由于 x=x+1 不是原子操作
// 所以應避免多個協程對x進行操作
// 使用容量為1的信道可以達到鎖的效果
func increment(ch chan bool, x *int) {
ch <- true
*x = *x + 1
<- ch
}
func main() {
// 注意要設定容量為 1 的緩沖信道
pipline := make(chan bool, 1)
var x int
for i:=0;i<1000;i++{
go increment(pipline, &x)
}
// 確保所有的協程都已完成
// 以后會介紹一種更合適的方法(Mutex),這里暫時使用sleep
time.Sleep(3)
fmt.Println("x 的值:", x)
}
輸出如下
x 的值:1000
如果不加鎖,輸出會小于1000,
系列導讀
01. 開發環境的搭建(Goland & VS Code)
02. 學習五種變數創建的方法
03. 詳解資料型別:****整形與浮點型
04. 詳解資料型別:byte、rune與string
05. 詳解資料型別:陣列與切片
06. 詳解資料型別:字典與布爾型別
07. 詳解資料型別:指標
08. 面向物件編程:結構體與繼承
09. 一篇文章理解 Go 里的函式
10. Go語言流程控制:if-else 條件陳述句
11. Go語言流程控制:switch-case 選擇陳述句
12. Go語言流程控制:for 回圈陳述句
13. Go語言流程控制:goto 無條件跳轉
14. Go語言流程控制:defer 延遲呼叫
15. 面向物件編程:介面與多型
16. 關鍵字:make 和 new 的區別?
17. 一篇文章理解 Go 里的陳述句塊與作用域
18. 學習 Go 協程:goroutine
19. 學習 Go 協程:詳解信道/通道
20. 幾個信道死鎖經典錯誤案例詳解
21. 學習 Go 協程:WaitGroup
22. 學習 Go 協程:互斥鎖和讀寫鎖
23. Go 里的例外處理:panic 和 recover
24. 超詳細解讀 Go Modules 前世今生及入門使用
25. Go 語言中關于包匯入必學的 8 個知識點
26. 如何開源自己寫的模塊給別人用?
27. 說說 Go 語言中的型別斷言?
28. 這五點帶你理解Go語言的select用法

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/6562.html
標籤:Go
上一篇:Go基礎語法
