Go語言入門學(xué)習(xí)之Channel通道詳解
前言
不同于傳統(tǒng)的多線程并發(fā)模型使用共享內(nèi)存來實現(xiàn)線程間通信的方式,go
是通過 channel
進(jìn)行協(xié)程 (goroutine
) 之間的通信來實現(xiàn)數(shù)據(jù)共享。
channel
,就是一個管道,可以想像成 Go
協(xié)程之間通信的管道。它是一種隊列式的數(shù)據(jù)結(jié)構(gòu),遵循先入先出的規(guī)則。
通道的聲明
每個通道都只能傳遞一種數(shù)據(jù)類型的數(shù)據(jù),聲明時需要指定通道的類型。chan Type
表示 Type
類型的通道。通道的零值為 nil
。
var channel_name chan channel_types
var str chan string
通道的初始化
聲明完通道后,通道的值為 nil
,不能直接使用,使用 make
函數(shù)對通道進(jìn)行初始化操作。
channel_name = make(chan channel_type)
str = make(chan string)
或者
str := make(chan string)
發(fā)送和接收數(shù)據(jù)
發(fā)送數(shù)據(jù),把 data 數(shù)據(jù)發(fā)送到 channel_name 通道中。
channel_name <- data
接收數(shù)據(jù),從 channel_name 通道中接收數(shù)據(jù)到 value。
value := <- channel_name
func PrintFunc(c chan string) { c <- "往通道里面?zhèn)鲾?shù)據(jù)" } func main() { str := make(chan string) fmt.Println("start") go PrintFunc(str) result := <-str fmt.Println(result) fmt.Println("end") }
發(fā)送與接收默認(rèn)是阻塞的。如果從通道接收數(shù)據(jù)沒接收完主協(xié)程是不會繼續(xù)執(zhí)行下去的。當(dāng)把數(shù)據(jù)發(fā)送到通道時,會在發(fā)送數(shù)據(jù)的語句處發(fā)生阻塞,直到有其它協(xié)程從通道讀取到數(shù)據(jù),才會解除阻塞。與此類似,當(dāng)讀取通道的數(shù)據(jù)時,如果沒有其它的協(xié)程把數(shù)據(jù)寫入到這個通道,那么讀取過程就會一直阻塞著。
通道的關(guān)閉
對于一個已經(jīng)使用完畢的通道,我們要將其進(jìn)行關(guān)閉。對于一個已經(jīng)關(guān)閉的通道如果再次關(guān)閉會導(dǎo)致報錯。
close(channel_name)
可以在接收數(shù)據(jù)時,判斷通道是否已經(jīng)關(guān)閉,從通道讀取數(shù)據(jù)返回的第二個值表示通道是否沒被關(guān)閉,如果已經(jīng)關(guān)閉,返回值為 false
;如果還未關(guān)閉,返回值為 true
。
value, ok := <- channel_name
通道的容量與長度
通道可以設(shè)置緩沖區(qū),通過 make 的第二個參數(shù)指定緩沖區(qū)大小
ch := make(chan int, 100)
0
:通道中不能存放數(shù)據(jù),在發(fā)送數(shù)據(jù)時,必須要求立馬接收,否則會報錯。此時的通道稱之為無緩沖通道。1
:通道只能緩存一個數(shù)據(jù),若通道中已有一個數(shù)據(jù),此時再往里發(fā)送數(shù)據(jù),會造成程序阻塞。利用這點可以利用通道來做鎖。- 大于
1
:通道中可以存放多個數(shù)據(jù),可以用于多個協(xié)程之間的通信管道,共享資源。
通過 cap
函數(shù)和 len
函數(shù)獲取通道的容量和長度。
func main() { // 創(chuàng)建一個通道 c := make(chan int, 5) fmt.Println("初始化:") fmt.Println("cap:", cap(c)) fmt.Println("len:", len(c)) c <- 1 c <- 2 c <- 3 fmt.Println("傳入數(shù)據(jù):") fmt.Println("cap:", cap(c)) fmt.Println("len:", len(c)) <-c fmt.Println("取出一個數(shù):") fmt.Println("cap:", cap(c)) fmt.Println("len:", len(c)) }
緩沖通道與無緩沖通道
帶緩沖區(qū)的通道允許發(fā)送端的數(shù)據(jù)發(fā)送和接收端的數(shù)據(jù)獲取處于異步狀態(tài),就是說發(fā)送端發(fā)送的數(shù)據(jù)可以放在緩沖區(qū)里面,可以等待接收端去獲取數(shù)據(jù),而不是立刻需要接收端去獲取數(shù)據(jù)。
不過由于緩沖區(qū)的大小是有限的,所以還是必須有接收端來接收數(shù)據(jù)的,否則緩沖區(qū)一滿,數(shù)據(jù)發(fā)送端就無法再發(fā)送數(shù)據(jù)了。
通道不帶緩沖,發(fā)送方會阻塞直到接收方從通道中接收了值。如果通道帶緩沖,發(fā)送方則會阻塞直到發(fā)送的值被拷貝到緩沖區(qū)內(nèi);如果緩沖區(qū)已滿,則意味著需要等待直到某個接收方獲取到一個值。接收方在有值可以接收之前會一直阻塞。
c := make(chan int) // 或者 c := make(chan int, 0)
緩沖通道允許通道里存儲一個或多個數(shù)據(jù),設(shè)置緩沖區(qū)后,發(fā)送端和接收端可以處于異步的狀態(tài)。
c := make(chan int, 3)
雙向通道和單向通道
雙向通道:既可以發(fā)送數(shù)據(jù)也可以接收數(shù)據(jù)
func main() { // 創(chuàng)建一個通道 c := make(chan int) // 發(fā)送數(shù)據(jù) go func() { fmt.Println("send: 1") c <- 1 }() // 接收數(shù)據(jù) go func() { n := <-c fmt.Println("receive:", n) }() // 主協(xié)程休眠 time.Sleep(time.Millisecond) }
單向通道:只能發(fā)送或者接收數(shù)據(jù)。具體細(xì)分為只讀通道和只寫通道。
<-chan
表示只讀通道:
// 定義只讀通道 c := make(chan string) // 定義類型 type Receiver = <-chan string var receiver Receiver = c // 或者簡單寫成下面的形式 type Receiver = <-chan int receiver := make(Receiver)
chan<-
表示只寫通道:
// 定義只寫通道 c := make(chan int) // 定義類型 type Sender = chan<- int var sender Sender = c // 或者簡單寫成下面的形式 type Sender = chan<- int sender := make(Sender)
package main import ( "fmt" "time" ) // Sender 只寫通道類型 type Sender = chan<- string // Receiver 只讀通道類型 type Receiver = <-chan string func main() { // 創(chuàng)建一個雙向通道 var ch = make(chan string) // 開啟一個協(xié)程 go func() { // 只寫通道 var sender Sender = ch fmt.Println("write only start:") sender <- "Go" }() // 開啟一個協(xié)程 go func() { // 只讀通道 var receiver Receiver = ch message := <-receiver fmt.Println("readonly start: ", message) }() time.Sleep(time.Millisecond) }
遍歷通道
使用 for range
循環(huán)可以遍歷通道,但在遍歷時要確保通道是處于關(guān)閉狀態(tài),否則循環(huán)會被阻塞。
package main import ( "fmt" ) func loopPrint(c chan int) { for i := 0; i < 10; i++ { c <- i } // 記得要關(guān)閉通道 // 否則主協(xié)程遍歷完不會結(jié)束,而會阻塞 close(c) } func main() { // 創(chuàng)建一個通道 var ch2 = make(chan int, 5) go loopPrint(ch2) for v := range ch2 { fmt.Println(v) } }
fibonacci 數(shù)列
package main import ( "fmt" ) func fibonacci(n int, c chan int) { x, y := 0, 1 for i := 0; i < n; i++ { c <- x x, y = y, x+y } close(c) } func main() { c := make(chan int, 10) go fibonacci(cap(c), c) // range 函數(shù)遍歷每個從通道接收到的數(shù)據(jù),因為 c 在發(fā)送完 10 個 // 數(shù)據(jù)之后就關(guān)閉了通道,所以這里我們 range 函數(shù)在接收到 10 個數(shù)據(jù) // 之后就結(jié)束了。如果上面的 c 通道不關(guān)閉,那么 range 函數(shù)就不 // 會結(jié)束,從而在接收第 11 個數(shù)據(jù)的時候就阻塞了。 for i := range c { fmt.Println(i) } }
參考文章:
總結(jié)
到此這篇關(guān)于Go語言入門學(xué)習(xí)之Channel通道的文章就介紹到這了,更多相關(guān)Go語言Channel通道內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang 實現(xiàn)interface{}轉(zhuǎn)其他類型操作
這篇文章主要介紹了golang 實現(xiàn)interface{}轉(zhuǎn)其他類型操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12Go語言基礎(chǔ)學(xué)習(xí)之Context的使用詳解
在Go語言中,Context是一個非常重要的概念,它用于在不同的?goroutine?之間傳遞請求域的相關(guān)數(shù)據(jù),本文將深入探討Go語言中?Context特性和Context的高級使用方法,希望對大家有所幫助2023-05-05golang實現(xiàn)unicode轉(zhuǎn)換為字符串string的方法
這篇文章主要介紹了golang實現(xiàn)unicode轉(zhuǎn)換為字符串string的方法,實例分析了Go語言編碼轉(zhuǎn)換的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2016-07-07淺析Golang開發(fā)中g(shù)oroutine的正確使用姿勢
很多初級的Gopher在學(xué)習(xí)了goroutine之后,在項目中其實使用率不高,所以這篇文章小編主要來帶大家深入了解一下goroutine的常見使用方法,希望對大家有所幫助2024-03-03Golang實現(xiàn)Json分級解析及數(shù)字解析實踐詳解
你是否遇到過在無法準(zhǔn)確確定json層級關(guān)系的情況下對json進(jìn)行解析的需求呢?本文就來和大家介紹一次解析不確定的json對象的經(jīng)歷,以及遇到的問題和解決方法2023-02-02