圖文詳解Go中的channel
channel
Go語(yǔ)言中的通道(channel)是一種特殊的類型。
在任何時(shí)候,同時(shí)只能有一個(gè) goroutine 訪問(wèn)通道進(jìn)行發(fā)送和獲取數(shù)據(jù)。goroutine 間通過(guò)通道就可以通信。
通道像一個(gè)傳送帶或者隊(duì)列,總是遵循先入先出(First In First Out)的規(guī)則,保證收發(fā)數(shù)據(jù)的順序。
(1)channel本身是一個(gè)隊(duì)列,先進(jìn)先出
(2)線程安全,不需要加鎖
(3)本身是有類型的,string, int 等,如果要存多種類型,則定義成 interface類型
(4)channel是引用類型,必須make之后才能使用,一旦 make,它的容量就確定了,不會(huì)動(dòng)態(tài)增加!!它和map,slice不一樣
特點(diǎn):
(1)一旦初始化容量,就不會(huì)改變了。
(2)當(dāng)寫(xiě)滿時(shí),不可以寫(xiě),取空時(shí),不可以取。
(3)發(fā)送將持續(xù)阻塞直到數(shù)據(jù)被接收
把數(shù)據(jù)往通道中發(fā)送時(shí),如果接收方一直都沒(méi)有接收,那么發(fā)送操作將持續(xù)阻塞。Go 程序運(yùn)行時(shí)能智能地發(fā)現(xiàn)一些永遠(yuǎn)無(wú)法發(fā)送成功的語(yǔ)句并做出提示
(4)接收將持續(xù)阻塞直到發(fā)送方發(fā)送數(shù)據(jù)。
如果接收方接收時(shí),通道中沒(méi)有發(fā)送方發(fā)送數(shù)據(jù),接收方也會(huì)發(fā)生阻塞,直到發(fā)送方發(fā)送數(shù)據(jù)為止。
(5)每次接收一個(gè)元素。
通道一次只能接收一個(gè)數(shù)據(jù)元素。
1、關(guān)于 channel的聲明和使用的代碼:
package main
import (
"fmt"
)
func main() {
//演示一下管道的使用
//1. 創(chuàng)建一個(gè)可以存放3個(gè)int類型的管道
var intChan chan int
intChan = make(chan int, 3)
//2. 看看intChan是什么
fmt.Printf("intChan 的值=%v intChan本身的地址=%p\n", intChan, &intChan)
//3. 向管道寫(xiě)入數(shù)據(jù)
intChan<- 10
num := 211
intChan<- num
intChan<- 50
// //如果從channel取出數(shù)據(jù)后,可以繼續(xù)放入
<-intChan
intChan<- 98//注意點(diǎn), 當(dāng)我們給管寫(xiě)入數(shù)據(jù)時(shí),不能超過(guò)其容量
//4. 看看管道的長(zhǎng)度和cap(容量)
fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan)) // 3, 3
//5. 從管道中讀取數(shù)據(jù)
var num2 int
num2 = <-intChan
fmt.Println("num2=", num2)
fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan)) // 2, 3
//6. 在沒(méi)有使用協(xié)程的情況下,如果我們的管道數(shù)據(jù)已經(jīng)全部取出,再取就會(huì)報(bào)告 deadlock
num3 := <-intChan
num4 := <-intChan
//num5 := <-intChan
fmt.Println("num3=", num3, "num4=", num4/*, "num5=", num5*/)
}
fmt.Printf("intChan 的值=%v intChan本身的地址=%p\n", intChan, &intChan)
這句代碼顯示:channel其實(shí)和指針一樣,本身存放在一個(gè)內(nèi)存單元中,有它的地址,而它的值是一個(gè) int類型的地址。
2、注意空接口類型的 channel
package main
import (
"fmt"
)
type Cat struct {
Name string
Age int
}
func main() {
//定義一個(gè)存放任意數(shù)據(jù)類型的管道 3個(gè)數(shù)據(jù)
//var allChan chan interface{}
allChan := make(chan interface{}, 3)
allChan<- 10
allChan<- "tom jack"
cat := Cat{"小花貓", 4}
allChan<- cat
//我們希望獲得到管道中的第三個(gè)元素,則先將前2個(gè)推出
<-allChan
<-allChan
newCat := <-allChan //從管道中取出的Cat是什么?
fmt.Printf("newCat=%T , newCat=%v\n", newCat, newCat)
//下面的寫(xiě)法是錯(cuò)誤的!編譯不通過(guò)
//fmt.Printf("newCat.Name=%v", newCat.Name)
//使用類型斷言
a := newCat.(Cat)
fmt.Printf("newCat.Name=%v", a.Name)
}
定義 interface類型的空接口,可以接收任意類型的數(shù)據(jù),但是在取出來(lái)的時(shí)候,必須斷言!
a := newCat.(Cat)
3、channel的關(guān)閉:close( )
關(guān)閉之后,不能再寫(xiě)入,只能讀。
只能由發(fā)送者執(zhí)行這句代碼
4、channel的遍歷: for … range
通道的數(shù)據(jù)接收一共有以下 4 種寫(xiě)法。
1.阻塞接收數(shù)據(jù)
阻塞模式接收數(shù)據(jù)時(shí),將接收變量作為<-操作符的左值,格式如下:
data := <-ch
執(zhí)行該語(yǔ)句時(shí)將會(huì)阻塞,直到接收到數(shù)據(jù)并賦值給 data 變量。
2.非阻塞接收數(shù)據(jù)(有問(wèn)題啊,還是會(huì)報(bào)錯(cuò)deadlock)
使用非阻塞方式從通道接收數(shù)據(jù)時(shí),語(yǔ)句不會(huì)發(fā)生阻塞,格式如下:
data, ok := <-ch
data:表示接收到的數(shù)據(jù)。未接收到數(shù)據(jù)時(shí),data 為通道類型的零值。
ok:表示是否接收到數(shù)據(jù)。
非阻塞的通道接收方法可能造成高的 CPU 占用,因此使用非常少。如果需要實(shí)現(xiàn)接收超時(shí)檢測(cè),可以配合 select 和計(jì)時(shí)器 channel進(jìn)行,可以參見(jiàn)后面的內(nèi)容。
3.接收任意數(shù)據(jù),忽略接收的數(shù)據(jù)
阻塞接收數(shù)據(jù)后,忽略從通道返回的數(shù)據(jù),格式如下:
<-ch
執(zhí)行該語(yǔ)句時(shí)將會(huì)發(fā)生阻塞,直到接收到數(shù)據(jù),但接收到的數(shù)據(jù)會(huì)被忽略。這個(gè)方式實(shí)際上只是通過(guò)通道在 goroutine 間阻塞收發(fā)實(shí)現(xiàn)并發(fā)同步。
使用通道做并發(fā)同步的寫(xiě)法,可以參考下面的例子:
package main
import (
"fmt"
)
func main() {
// 構(gòu)建一個(gè)通道
ch := make(chan int)
// 開(kāi)啟一個(gè)并發(fā)匿名函數(shù)
go func() {
fmt.Println("start goroutine")
// 通過(guò)通道通知main的goroutine
ch <- 0
fmt.Println("exit goroutine")
}()
fmt.Println("wait goroutine")
// 等待匿名goroutine
<-ch
fmt.Println("all done")
}
4.循環(huán)接收
通道的數(shù)據(jù)接收可以借用 for range 語(yǔ)句進(jìn)行多個(gè)元素的接收操作,格式如下:
for data := range ch {
}
通道 ch 是可以進(jìn)行遍歷的,遍歷的結(jié)果就是接收到的數(shù)據(jù)。數(shù)據(jù)類型就是通道的數(shù)據(jù)類型。通過(guò) for 遍歷獲得的變量只有一個(gè),即上面例子中的 data。
package main
import (
"fmt"
)
func main() {
intChan := make(chan int, 3)
intChan<- 100
intChan<- 200
close(intChan) // close
//這時(shí)不能夠再寫(xiě)入數(shù)到channel
//intChan<- 300
fmt.Println("okook~")
//當(dāng)管道關(guān)閉后,讀取數(shù)據(jù)是可以的
n1 := <-intChan
fmt.Println("n1=", n1)
//遍歷管道
intChan2 := make(chan int, 100)
for i := 0; i < 100; i++ {
intChan2<- i * 2 //放入100個(gè)數(shù)據(jù)到管道
}
//遍歷管道不能使用普通的 for 循環(huán)
// for i := 0; i < len(intChan2); i++ {
// }
//在遍歷時(shí),如果channel沒(méi)有關(guān)閉,則會(huì)出現(xiàn)deadlock的錯(cuò)誤
//在遍歷時(shí),如果channel已經(jīng)關(guān)閉,則會(huì)正常遍歷數(shù)據(jù),遍歷完后,就會(huì)退出遍歷
close(intChan2)
for v := range intChan2 { //沒(méi)有下標(biāo)
fmt.Println("v=", v)
}
}
在遍歷管道之前要先關(guān)閉管道,不然會(huì)出現(xiàn)deadlock的錯(cuò)誤
應(yīng)用1


開(kāi)兩個(gè)管道;
當(dāng)寫(xiě)協(xié)程完成工作之后,close數(shù)據(jù)管道,讀協(xié)程對(duì)數(shù)據(jù)管道 intChan的數(shù)據(jù)讀完之后,就向退出管道 exitChan 寫(xiě)入一個(gè) true,close掉;
主線程循環(huán)檢測(cè)退出管道里是否有數(shù)據(jù),如果有,說(shuō)明讀協(xié)程完成,主程序就可以退出了。
package main
import (
"fmt"
)
//write Data
func writeData(intChan chan int) {
for i := 1; i <= 50; i++ {
//放入數(shù)據(jù)
intChan <- i //
fmt.Println("writeData ", i)
//time.Sleep(time.Second)
}
close(intChan) //關(guān)閉
}
//read data
func readData(intChan chan int, exitChan chan bool) {
for {
v, ok := <-intChan
if !ok {
break
}
// time.Sleep(time.Second)
fmt.Printf("readData 讀到數(shù)據(jù)=%v\n", v)
}
//readData 讀取完數(shù)據(jù)后,即任務(wù)完成
exitChan <- true
close(exitChan)
}
func main() {
//創(chuàng)建兩個(gè)管道
intChan := make(chan int, 10)
exitChan := make(chan bool, 1)
go writeData(intChan)
go readData(intChan, exitChan)
//time.Sleep(time.Second * 10)
for {
_, ok := <-exitChan
if !ok {
break
}
}
}
應(yīng)用2


定義三個(gè)管道:
- intChan :放8000個(gè)數(shù)
- primeChan:放素?cái)?shù)
- exitChan :4個(gè)協(xié)程運(yùn)行完畢的標(biāo)志
package main
import (
"fmt"
"time"
)
//向 intChan放入 1-8000個(gè)數(shù)
func putNum(intChan chan int) {
for i := 1; i <= 80000; i++ {
intChan <- i
}
//關(guān)閉intChan
close(intChan)
}
// 從 intChan取出數(shù)據(jù),并判斷是否為素?cái)?shù),如果是,就
// //放入到primeChan
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) {
//使用for 循環(huán)
// var num int
var flag bool //
for {
//time.Sleep(time.Millisecond * 10)
num, ok := <-intChan //intChan 取不到..
if !ok {
break
}
flag = true //假設(shè)是素?cái)?shù)
//判斷num是不是素?cái)?shù)
for i := 2; i < num; i++ {
if num%i == 0 { //說(shuō)明該num不是素?cái)?shù)
flag = false
break
}
}
if flag {
//將這個(gè)數(shù)就放入到primeChan
primeChan <- num
}
}
fmt.Println("有一個(gè)primeNum 協(xié)程因?yàn)槿〔坏綌?shù)據(jù),退出")
//這里我們還不能關(guān)閉 primeChan
//向 exitChan 寫(xiě)入true
exitChan <- true
}
func main() {
intChan := make(chan int, 1000)
primeChan := make(chan int, 20000) //放入結(jié)果
//標(biāo)識(shí)退出的管道
exitChan := make(chan bool, 4) // 4個(gè)
start := time.Now().Unix()
//開(kāi)啟一個(gè)協(xié)程,向 intChan放入 1-8000個(gè)數(shù)
go putNum(intChan)
//開(kāi)啟4個(gè)協(xié)程,從 intChan取出數(shù)據(jù),并判斷是否為素?cái)?shù),如果是,就
//放入到primeChan
for i := 0; i < 4; i++ {
go primeNum(intChan, primeChan, exitChan)
}
//這里我們主線程,進(jìn)行處理
//直接
go func() {
for i := 0; i < 4; i++ {
<-exitChan
}
end := time.Now().Unix()
fmt.Println("使用協(xié)程耗時(shí)=", end-start)
//當(dāng)我們從exitChan 取出了4個(gè)結(jié)果,就可以放心的關(guān)閉 prprimeChan
close(primeChan)
}()
//遍歷我們的 primeChan ,把結(jié)果取出
for {
res, ok := <-primeChan
if !ok {
break
}
//將結(jié)果輸出
fmt.Printf("素?cái)?shù)=%d\n", res)
}
fmt.Println("main線程退出")
}
有一個(gè)primeNum 協(xié)程因?yàn)槿〔坏綌?shù)據(jù),退出
有一個(gè)primeNum 協(xié)程因?yàn)槿〔坏綌?shù)據(jù),退出
有一個(gè)primeNum 協(xié)程因?yàn)槿〔坏綌?shù)據(jù),退出
有一個(gè)primeNum 協(xié)程因?yàn)槿〔坏綌?shù)據(jù),退出
使用協(xié)程耗時(shí)= 3
main線程退出
存數(shù)字和計(jì)算素?cái)?shù)比較簡(jiǎn)單,不提
開(kāi)啟4個(gè)協(xié)程,運(yùn)算素?cái)?shù),效率比單個(gè)線程高幾倍!
go func() {
for i := 0; i < 4; i++ {
<-exitChan
}
end := time.Now().Unix()
fmt.Println("使用協(xié)程耗時(shí)=", end-start)
//當(dāng)我們從exitChan 取出了4個(gè)結(jié)果,就可以放心的關(guān)閉 prprimeChan
close(primeChan)
}()
這里定義了一個(gè)匿名協(xié)程,作用是檢測(cè)4個(gè)協(xié)程 有沒(méi)有完成運(yùn)行,取不出來(lái)就會(huì)阻塞,等待協(xié)程完成。也可以這樣:
if len(exitChan) == 4
總結(jié)
到此這篇關(guān)于圖文詳解Go中的channel的文章就介紹到這了,更多相關(guān)Go中channel內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang多維度排序及題解最長(zhǎng)連續(xù)序列
這篇文章主要為大家介紹了golang多維度排序及題解最長(zhǎng)連續(xù)序列示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10
Go?Ticker?周期性定時(shí)器用法及實(shí)現(xiàn)原理詳解
這篇文章主要為大家介紹了Go?Ticker?周期性定時(shí)器用法及實(shí)現(xiàn)原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
Go 1.21新增的slices包中切片函數(shù)用法詳解
Go 1.21新增的 slices 包提供了很多和切片相關(guān)的函數(shù),可以用于任何類型的切片,本文通過(guò)代碼示例為大家介紹了部分切片函數(shù)的具體用法,感興趣的小伙伴可以了解一下2023-08-08
golang 的string與[]byte轉(zhuǎn)換方式
這篇文章主要介紹了golang 的string與[]byte轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04
Goland使用delve進(jìn)行遠(yuǎn)程調(diào)試的詳細(xì)教程
網(wǎng)上給出的使用delve進(jìn)行遠(yuǎn)程調(diào)試,都需要先在本地交叉編譯或者在遠(yuǎn)程主機(jī)上編譯出可運(yùn)行的程序,然后再用delve在遠(yuǎn)程啟動(dòng)程序,本教程會(huì)將上面的步驟簡(jiǎn)化為只需要兩步,1,在遠(yuǎn)程運(yùn)行程序2,在本地啟動(dòng)調(diào)試,需要的朋友可以參考下2024-08-08
Go語(yǔ)言學(xué)習(xí)筆記之錯(cuò)誤和異常詳解
Go語(yǔ)言采用返回值的形式來(lái)返回錯(cuò)誤,這一機(jī)制既可以讓開(kāi)發(fā)者真正理解錯(cuò)誤處理的含義,也可以大大降低程序的復(fù)雜度,下面這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言學(xué)習(xí)筆記之錯(cuò)誤和異常的相關(guān)資料,需要的朋友可以參考下2022-07-07

