詳解Golang中Channel的原理和使用技巧
Channel 詳解
Channel 簡(jiǎn)要說明
Channel(一般簡(jiǎn)寫為 chan) 管道提供了一種機(jī)制,它在兩個(gè)并發(fā)執(zhí)行的協(xié)程之間進(jìn)行同步,并通過傳遞與該管道元素類型相符的值來進(jìn)行通信。Channel 是用來在不同的 goroutine 中交換數(shù)據(jù)的,千萬不要把 Channel 拿來在同一個(gè) goroutine 中的不同函數(shù)之間間交換數(shù)據(jù),chan 可以理解為一個(gè)管道或者先進(jìn)先出的隊(duì)列。
Channel 類型定義
最簡(jiǎn)單形式: chan elementType,通過這個(gè)類型的值,你可以發(fā)送和接收elementType 類型的元素。Channel 是引用類型,如果將一個(gè) chan 變量賦值給另外一個(gè),則這兩個(gè)變量訪問的是相同的 chann。
當(dāng)然,我們可以用 make 分配一個(gè)channel:var c = make(chan int)
Channel 操作符<- 和操作方式
通信操作符 <- 的箭頭指示數(shù)據(jù)流向,箭頭指向哪里,數(shù)據(jù)就流向哪里,它是一個(gè)二元操作符,可以支持任意類型,對(duì)于 channel 的操作只有4種方式:
1.創(chuàng)建 channel (通過make()函數(shù)實(shí)現(xiàn),包括無緩存 channel 和有緩存 channel);
2.向 channel 中添加數(shù)據(jù)(channel<-data);
3.從 channel 中讀取數(shù)據(jù)(data<-channel);
- data<-channel, 從 channel 中接收數(shù)據(jù)并賦值給 data
- <-channel,從 channel 中接收數(shù)據(jù)并丟棄
4.關(guān)閉 channel(通過 close()函數(shù)實(shí)現(xiàn))
- 讀取關(guān)閉后的無緩存通道,不管通道中是否有數(shù)據(jù),返回值都為 0 和 false。
- 讀取關(guān)閉后的有緩存通道,將緩存數(shù)據(jù)讀取完后,再讀取返回值為 0 和 false。
- 對(duì)于一個(gè)關(guān)閉的 channel,如果繼續(xù)向 channel 發(fā)送數(shù)據(jù),會(huì)引起 panic
- channel 不能 close 兩次,多次 close 會(huì) panic
Channel 有無緩沖 & 同步、異步
channel 分為有緩沖 channel 和無緩沖 channel,兩種 channel 的創(chuàng)建方法如下:
var ch = make(chan int) //無緩沖 channel,等同于make(chan int ,0),是一個(gè)同步的 Channel
- 無緩沖 channel 在讀和寫的過程中是都會(huì)阻塞,由于阻塞的存在,所以使用 channel 時(shí)特別注意使用方法,防止死鎖和協(xié)程泄漏的產(chǎn)生。
- 無緩沖 channel 的發(fā)送動(dòng)作一直要到有一個(gè)接收者接收這個(gè)值才算完成,否則都是阻塞著的,也就是說,發(fā)送的數(shù)據(jù)需要被讀取后,發(fā)送才會(huì)完成
- 一般要配合 select + timeout 處理,然后再在這里添加超時(shí)時(shí)間
var ch = make(chan int,10) //有緩沖channel,緩沖大小是10,是一個(gè)異步的Channel
- 帶緩存的 channel 實(shí)際上是一個(gè)阻塞隊(duì)列。隊(duì)列滿時(shí)寫協(xié)程會(huì)阻塞,隊(duì)列空時(shí)讀協(xié)程阻塞。
- 有緩沖的時(shí)候,寫操作是寫完之后直接返回的。相對(duì)于不帶緩存 channel,帶緩存 channel 不易造成死鎖。
Channel 各種操作導(dǎo)致阻塞和協(xié)程泄漏的場(chǎng)景
寫操作,什么時(shí)候會(huì)被阻塞?
1.向 nil 通道發(fā)送數(shù)據(jù)會(huì)被阻塞
2.向無緩沖 channel 寫數(shù)據(jù),如果讀協(xié)程沒有準(zhǔn)備好,會(huì)阻塞
- 無緩沖 channel ,必須要有讀有寫,寫了數(shù)據(jù)之后,必須要讀出來,否則導(dǎo)致 channel 阻塞,從而使得協(xié)程阻塞而使得協(xié)程泄漏
- 一個(gè)無緩沖 channel,如果每次來一個(gè)請(qǐng)求就開一個(gè) go 協(xié)程往里面寫數(shù)據(jù),但是一直沒有被讀取,那么就會(huì)導(dǎo)致這個(gè) chan 一直阻塞,使得寫這個(gè) chan 的 go 協(xié)程一直無法釋放從而協(xié)程泄漏。
3.向有緩沖 channel 寫數(shù)據(jù),如果緩沖已滿,會(huì)阻塞
有緩沖的 channel,在緩沖 buffer 之內(nèi),不讀取也不會(huì)導(dǎo)致阻塞,當(dāng)然也就不會(huì)使得協(xié)程泄漏,但是如果寫數(shù)據(jù)超過了 buffer 還沒有讀取,那么繼續(xù)寫的時(shí)候就會(huì)阻塞了。如果往有緩沖的 channel 寫了數(shù)據(jù)但是一直沒有讀取就直接退出協(xié)程的話,一樣會(huì)導(dǎo)致 channel 阻塞,從而使得協(xié)程阻塞并泄漏。
讀操作,什么時(shí)候會(huì)被阻塞?
- 從 nil 通道接收數(shù)據(jù)會(huì)被阻塞
- 從無緩沖 channel 讀數(shù)據(jù),如果寫協(xié)程沒有準(zhǔn)備好,會(huì)阻塞
- 從有緩沖 channel 讀數(shù)據(jù),如果緩沖為空,會(huì)阻塞
close 操作,什么時(shí)候會(huì)被阻塞?
close channel 對(duì) channel 阻塞是沒有任何效果的,寫了數(shù)據(jù)但是不讀,直接 close,還是會(huì)阻塞的。
Channel 各種操作對(duì)應(yīng)的狀態(tài)
- 正常的 channel,可讀、可寫
- nil 的 channel,表示未初始化的狀態(tài),只進(jìn)行了聲明,或者手動(dòng)賦值為 nil
- 已經(jīng) closed 的 channel,表示已經(jīng) close 關(guān)閉了,千萬不要誤認(rèn)為關(guān)閉 channel 后,channel 的值是 nil

Channel 長(zhǎng)度和容量
容量(capacity)代表 Channel 容納的最多的元素的數(shù)量,代表Channel的緩存的大小。如果沒有設(shè)置容量,或者容量設(shè)置為0, 說明 Channel 沒有緩存,長(zhǎng)度和容量的兩個(gè)函數(shù)是 cap 和 len 。
示例如下:
c := make(chan int, 100) // cap 就是 100,但是此時(shí) len 為 0 c <- 0 // len = 1, cap = 100 c <- 0 // len = 2, cap = 100 <- c // len = 1, cap = 100
Channel 的缺點(diǎn)
Channel 的缺點(diǎn):
Channel 可能會(huì)導(dǎo)致循環(huán)阻塞或者協(xié)程泄漏,這個(gè)是最最最要重點(diǎn)關(guān)注的。
Channel 中傳遞指針會(huì)導(dǎo)致數(shù)據(jù)競(jìng)態(tài)問題(data race/ race conditions)
Channel 中傳遞的都是數(shù)據(jù)的拷貝,可能會(huì)影響性能,但是就目前我們的機(jī)器性能來看,這點(diǎn)數(shù)據(jù)拷貝所帶來的 CPU 消耗,大多數(shù)的情況下可以忽略。
Go Channel 實(shí)現(xiàn)協(xié)程同步
channel 實(shí)現(xiàn)并發(fā)同步的說明
channel 作為 Go 并發(fā)模型的核心思想:不要通過共享內(nèi)存來通信,而應(yīng)該通過通信來共享內(nèi)存,那么在 Go 里面,當(dāng)然也可以很方便通過 channel 來實(shí)現(xiàn)協(xié)程的并發(fā)和同步了,并且 channel 本身還可以支持有緩沖和無緩沖的,通過 channel + timeout 實(shí)現(xiàn)并發(fā)協(xié)程之間的同步也是常見的一種使用姿勢(shì)。
無緩沖 chan 示例
示例如下:
package main
import "fmt"
func main() {
var ch = make(chan string)
for i := 0; i < 10; i++ {
go sum(i, i+10, ch)
}
for i := 0; i < 10; i++ {
fmt.Print(<-ch)
}
}
func sum(start, end int, ch chan string) {
var sum int = 0
for i := start; i < end; i++ {
sum += i
}
ch <- fmt.Sprintf("Sum from %d to %d is %d\n", start, end, sum)
}有緩沖 chan 示例
message_chan := make(chan int, 2)
go func() {
time.Sleep(time.Second * 3)
println("start recv...")
println(<-message_chan)
println(<-message_chan)
println(<-message_chan)
println("finish recv...")
}()
println("start send 10...")
message_chan <- 10
println("start send 20...")
message_chan <- 20
println("start send 30...")
message_chan <- 30
println("finish send...")
time.Sleep(time.Second * 3)
close(message_chan)到此這篇關(guān)于詳解Golang中Channel的原理和使用技巧的文章就介紹到這了,更多相關(guān)Golang Channel內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang?使用chromedp獲取頁面請(qǐng)求日志network
這篇文章主要為大家介紹了golang?使用chromedp獲取頁面請(qǐng)求日志network方法實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
Golang調(diào)用FFmpeg轉(zhuǎn)換視頻流的實(shí)現(xiàn)
本文主要介紹了Golang調(diào)用FFmpeg轉(zhuǎn)換視頻流,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02
go-zero熔斷機(jī)制組件Breaker接口定義使用解析
這篇文章主要為大家介紹了go-zero熔斷機(jī)制組件Breaker接口定義使用解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05

