golang中channel通道的實(shí)現(xiàn)
簡介-是什么、有什么用
channel字面意思是“通道”,用于goroutine之間進(jìn)行通信、同步。
Goroutine 和 channel 是 Go 語言并發(fā)編程的兩大基石。Goroutine用于執(zhí)行并發(fā)任務(wù),channel用于 goroutine之間的通信、同步。
看個簡單demo:
package main
import (
"log"
"time"
)
var logger = log.Default()
func main() {
c1 := make(chan int)
go func() {
logger.Println("go sleep start")
time.Sleep(3 * time.Second)
logger.Println("go sleep end, send start")
c1 <- 1
logger.Println("send end")
}()
logger.Println("main receive start")
i, ok := <-c1
logger.Printf("main receive end, i:%d, ok:%t\n", i, ok)
}
運(yùn)行程序可以看到main協(xié)程從通道讀取值時(shí)會阻塞,直到另一個協(xié)程往通道發(fā)送數(shù)據(jù),才會喚醒main協(xié)程。
CSP模型
與主流語言通過共享內(nèi)存來進(jìn)行并發(fā)控制方式不同,Go 語言采用了 CSP 模型。
共享內(nèi)存存在競態(tài)問題,需要加鎖同步,會造成性能問題。
CSP (Communicating Sequential Process ),即通信順序進(jìn)程, 是一種并發(fā)編程模型,是一個很強(qiáng)大的并發(fā)數(shù)據(jù)模型,是上個世紀(jì)七十年代提出的,用于描述兩個獨(dú)立的并發(fā)實(shí)體通過共享的通訊 channel(管道)進(jìn)行通信的并發(fā)模型。
強(qiáng)調(diào)通信,有這么一句話:
不要通過共享內(nèi)存來通信,而要通過通信來實(shí)現(xiàn)內(nèi)存共享。
Go語言通過協(xié)程Goroutine和通道Channel實(shí)現(xiàn)了 CSP 模型。go語言并沒有完全實(shí)現(xiàn)了CSP模型的所有理論,僅借用了 process和channel這兩個概念(分別對應(yīng)go語言的goroutine和channel):process是并發(fā)執(zhí)行的實(shí)體,每個實(shí)體之間通過channel通訊來實(shí)現(xiàn)數(shù)據(jù)共享。
channel詳解
channel具有如下特性:
- 并發(fā)安全
- 先進(jìn)先出
- 能阻塞和喚醒goroutine
channel類型
channel是go語言的一種特殊類型,它是一個引用類型,因此未初始化時(shí)其默認(rèn)零值是nil。
- 定義channel變量:
var 變量名稱 chan 元素類型
- 元素類型,是指channel通道中元素的具體類型。
例子:
var c1 chan int var c2 chan string var c3 chan bool var c4 chan []int
- 初始化channel,使用
make:
未初始化,默認(rèn)零值是nil,對nil通道發(fā)送或接收都會一直阻塞。
make(chan 元素類型, [緩沖區(qū)容量])
- 緩沖區(qū)容量:是可選參數(shù),不指定或?yàn)?則創(chuàng)建無緩沖通道,指定且大于0則創(chuàng)建有緩沖通道。
例子:
c1 := make(chan int) c2 := make(chan int, 1)
channel的操作
三種操作:
- 發(fā)送(即寫操作):往通道中發(fā)送數(shù)據(jù)
- 接收(即讀操作):從通道中讀取數(shù)據(jù)
- 關(guān)閉
發(fā)送和接收使用<-符號。
發(fā)送
ch <- 10 // 把10發(fā)送到ch中
發(fā)送什么時(shí)候會阻塞?
當(dāng)通道未關(guān)閉且緩沖區(qū)滿時(shí),發(fā)送數(shù)據(jù)就會阻塞,直到有其他協(xié)程消費(fèi)數(shù)據(jù)使緩沖區(qū)不滿才不再阻塞。
接收
x := <- ch // 接收并賦值 <-ch // 僅接收
接收什么時(shí)候會阻塞?
當(dāng)通道未關(guān)閉且緩沖區(qū)空(無數(shù)據(jù)可讀)時(shí),接收數(shù)據(jù)就會阻塞,直到有其他協(xié)程向通道生產(chǎn)數(shù)據(jù)使緩沖區(qū)不空、有數(shù)據(jù)可讀時(shí)才不再阻塞。
多返回值模式,接收操作可以使用多返回值模式:
value, ok := <- ch
- ok:表示是否成功從通道中讀取數(shù)據(jù)。false,即未成功讀取,表示通道為空、無數(shù)據(jù)可讀了,且此時(shí)通道肯定關(guān)閉了,不然接收操作會阻塞。
注意:接收操作不阻塞,只有兩種情況:1. 有數(shù)據(jù)可讀、通道不空;2. 若無數(shù)據(jù)可讀,則通道必須關(guān)閉了
- value:從通道中讀取的值,若ok為false,則value是通道元素的數(shù)據(jù)類型的零值。
個人認(rèn)為,多返回值模式的作用:如果ok返回false,則表示通道空了且已關(guān)閉。
nil通道
chan類型的默認(rèn)零值是nil,對nil通道進(jìn)行讀、寫都會阻塞。
關(guān)閉通道
使用close函數(shù)
close(ch)
注意:
- 通常由發(fā)送方執(zhí)行關(guān)閉操作
- 關(guān)閉通道不是必須的(不像關(guān)閉文件是必須的),一般僅在接收方明確需要關(guān)閉信號時(shí)才執(zhí)行關(guān)閉操作
關(guān)閉后的通道有如下特點(diǎn):
- 不能再發(fā)送,再發(fā)送數(shù)據(jù)會panic
- 可以接收。通道被關(guān)閉,仍然可以讀取未讀完的數(shù)據(jù);當(dāng)通道為空(即沒有數(shù)據(jù)了),再讀取也不會造成阻塞,而是會返回對應(yīng)數(shù)據(jù)類型的零值
重要:已關(guān)閉通道,一定不會阻塞讀取操作
- 重復(fù)關(guān)閉會panic
close最佳實(shí)踐:
- 非必要不要close
- 比較常見的是將close作為一種通知機(jī)制,通過close告訴消費(fèi)者我關(guān)閉了,此時(shí)消費(fèi)者消費(fèi)就不會再阻塞了。
for range接收通道數(shù)據(jù)
for range可不斷從通道中接收數(shù)據(jù),當(dāng)通道為空且已關(guān)閉了,會退出循環(huán)(因?yàn)闉榭涨乙殃P(guān)閉的通道是不可能再有數(shù)據(jù)了);當(dāng)通道為空但未關(guān)閉,會阻塞直到通道有數(shù)據(jù)。
示例:
func main() {
c1 := make(chan int, 3)
go func() {
for i := 0; i < 10; i++ {
<-time.After(time.Second)
c1 <- i
}
<-time.After(5 * time.Second)
for i := 90; i < 100; i++ {
<-time.After(time.Second)
c1 <- i
}
<-time.After(5 * time.Second)
log.Default().Println("---close chan---")
close(c1)
}()
go func() {
for i := range c1 {
log.Default().Println(i)
}
log.Default().Println("---chan closed---")
}()
for {
}
}
單向通道
是什么?
只能發(fā)送或只能接收的通道就是單向通道。
為什么要單向通道?
某些場景下我們想限制使用者對通道的操作:只允許發(fā)送或接收,為了表明這種意圖并防止被濫用,所以go語言提供了單向channel。
如何使用?
定義單向通道:
箭頭<-和關(guān)鍵字chan的相對位置表明了當(dāng)前通道允許的操作,這種限制將在編譯階段進(jìn)行檢測。
<- chan int // 只接收通道,只能接收不能發(fā)送 chan <- int // 只發(fā)送通道,只能發(fā)送不能接收
「只接收channel」不允許close,因?yàn)橐话愣际前l(fā)送方來close通道。
通道類型轉(zhuǎn)換:
雙向通道可以轉(zhuǎn)成單向通道(是隱式轉(zhuǎn)換),但反之不行。
示例:
生產(chǎn)者只能向channel發(fā)送數(shù)據(jù),消費(fèi)者只能從channel接收數(shù)據(jù),所以使用單向channel
func main() {
c1 := make(chan int, 1)
go producer(c1)
go consumer(c1)
for {
}
}
func producer(ch chan<- int) {
for i := 0; i < 10; i++ {
<-time.After(time.Second)
ch <- i
}
log.Default().Println("producer. close chan")
// 發(fā)送通道可close
close(ch)
}
func consumer(ch <-chan int) {
for i := range ch {
log.Default().Println("consume:", i)
// 接收通過不能close
//close(ch)
}
log.Default().Println("consumer. chan closed")
}
select多路復(fù)用
作用:
可以同時(shí)監(jiān)聽多個channel的發(fā)送或接收操作。
如何使用?
- 類似switch語句,select也有一系列case分支和一個default分支,每個case分支可以監(jiān)聽一個channel的發(fā)送或接收操作,若可接收或可發(fā)送,則匹配該case分支;沒有任何case匹配,則走default分支。
- 若沒有default分支,select會一直阻塞直到某個case匹配。
- 若沒有case也沒有default分支,select會一直阻塞。
- 若同時(shí)有多個case滿足,select會隨機(jī)選擇一個case執(zhí)行。
使用方式如下:
select {
case <-ch1:
//...
case data := <-ch2:
//...
case ch3 <- 10:
//...
default:
//默認(rèn)操作
}
示例:
func main() {
ch := make(chan int, 1)
for i := 0; i < 10; i++ {
select {
case ch <- i:
log.Default().Println("send")
case x := <-ch:
log.Default().Println("receive, x:", x)
default:
log.Default().Println("default")
}
// 會panic panic: send on closed channel
//if i == 0 {
// close(ch)
//}
}
}
無緩沖channel和有緩沖channel
make(chan 元素類型, [緩沖區(qū)容量])
創(chuàng)建channel時(shí):
- 未指定緩沖區(qū)容量或?yàn)?,則創(chuàng)建的是無緩沖channel
- 指定了緩沖區(qū)容量且大于0,則創(chuàng)建的是有緩沖channel
無緩沖channel
沒有緩沖區(qū),channel中不能緩存數(shù)據(jù)。
- 發(fā)送操作一定會阻塞,直到有人接收
- 接收操作一定會阻塞,直到有人發(fā)送
有緩沖channel
有緩沖區(qū),channel中可以緩存數(shù)據(jù)。
- 僅當(dāng)緩沖區(qū)滿時(shí),發(fā)送操作才阻塞,直到有人接收
- 僅當(dāng)緩沖區(qū)空時(shí),接收操作才阻塞,直到有人發(fā)送
len和cap方法
- len方法可以返回通道中元素的個數(shù)
- cap方法可以返回通道的緩沖區(qū)容量
對于「無緩沖channel」,len和cap都是0。
問題
channel有一個發(fā)送方,多個接收方怎么辦?
多個接收方共同消費(fèi)channel中的數(shù)據(jù)。
示例:
func main() {
ch1 := make(chan int, 1)
go consumer(ch1)
go consumer2(ch1)
go producer(ch1)
for {
}
}
func producer(ch chan<- int) {
for i := 0; i < 10; i++ {
<-time.After(time.Second)
ch <- i
}
log.Default().Println("producer. close chan")
// 發(fā)送通道可close
close(ch)
log.Default().Println("producer. end")
}
func consumer(ch <-chan int) {
for i := range ch {
log.Default().Println("consumer consume:", i)
}
log.Default().Println("consumer. chan closed")
}
func consumer2(ch <-chan int) {
for i := range ch {
log.Default().Println("consumer2 consume:", i)
}
log.Default().Println("consumer2. chan closed")
}
運(yùn)行結(jié)果:
2022/10/30 01:46:46 consumer consume: 0
2022/10/30 01:46:47 consumer2 consume: 1
2022/10/30 01:46:48 consumer consume: 2
2022/10/30 01:46:49 consumer2 consume: 3
2022/10/30 01:46:50 consumer consume: 4
2022/10/30 01:46:51 consumer2 consume: 5
2022/10/30 01:46:52 consumer consume: 6
2022/10/30 01:46:53 consumer2 consume: 7
2022/10/30 01:46:54 consumer consume: 8
2022/10/30 01:46:55 consumer2 consume: 9
2022/10/30 01:46:55 producer. close chan
2022/10/30 01:46:55 producer. end
2022/10/30 01:46:55 consumer. chan closed
2022/10/30 01:46:55 consumer2. chan closed
goroutine泄漏
goroutine泄漏是什么?
是指goroutine一直阻塞而無法被回收。
不正確的使用channel可能導(dǎo)致goroutine泄漏,如下:
func main() {
ch := make(chan int)
go test(ch)
select {
case <-ch:
log.Default().Println("receive")
case <-time.After(time.Second):
log.Default().Println("time")
}
for {
}
}
func test(ch chan int) {
log.Default().Println("test run")
<-time.After(3 * time.Second)
ch <- 1
log.Default().Println("test end")
}
運(yùn)行結(jié)果:
2022/10/30 01:55:17 test run
2022/10/30 01:55:18 time
發(fā)生了goroutine泄漏,test方法的goroutine一直阻塞了,因?yàn)閙ain gorutine無法接收通道
到此這篇關(guān)于golang中channel通道的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)golang channel通道內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言metrics應(yīng)用監(jiān)控指標(biāo)基本使用說明
這篇文章主要為大家介紹了Go語言metrics應(yīng)用監(jiān)控指標(biāo)的基本使用說明,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-02-02
Golang實(shí)現(xiàn)心跳機(jī)制的示例詳解
這篇文章主要為大家詳細(xì)介紹了Golang實(shí)現(xiàn)心跳機(jī)制的相關(guān)知識,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考一下2024-04-04
go語言的panic和recover函數(shù)用法實(shí)例
今天小編就為大家分享一篇關(guān)于go語言的panic和recover函數(shù)用法實(shí)例,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-04-04
一文詳解如何在Golang中實(shí)現(xiàn)JWT認(rèn)證與授權(quán)
在現(xiàn)代Web應(yīng)用中,安全性是一個非常重要的課題,JWT作為一種常用的認(rèn)證與授權(quán)機(jī)制,已被廣泛應(yīng)用于各種系統(tǒng)中,下面我們就來看看如何在Golang中實(shí)現(xiàn)JWT認(rèn)證與授權(quán)吧2025-03-03

