go channel 缓冲区最大限制_[Go区块链基础]go channel
channel是進(jìn)程內(nèi)的通信方式,因此通過(guò)channel傳遞對(duì)象的過(guò)程和調(diào)用函數(shù)時(shí)的參數(shù)傳遞行為比較一致,比如也可以傳遞指針等。如果需要跨進(jìn)程通信,我們建議用分布式系統(tǒng)的方法來(lái)解決,比如使用Socket或者HTTP等通信協(xié)議。Go語(yǔ)言對(duì)于網(wǎng)絡(luò)方面也有非常完善的支持。
channel是類(lèi)型相關(guān)的。也就是說(shuō),一個(gè)channel只能傳遞一種類(lèi)型的值,這個(gè)類(lèi)型需要在聲明channel時(shí)指定。如果對(duì)Unix管道有所了解的話,就不難理解channel,可以將其認(rèn)為是一種類(lèi)型安全的管道。
在了解channel的語(yǔ)法前,我們先看下用channel的方式重寫(xiě)上面的例子是什么樣子的,以此對(duì)channel先有一個(gè)直感的認(rèn)識(shí)。
/** * @version: v1.0 * @author: Kandy.Ye * @contact: Kandy.Ye@foxmail.com * @file: main.go * @time: 2018/3/7 23:45 **/package mainimport "fmt"func Count(ch chan int) {ch <- 1fmt.Println("Counting") }func main() {chs := make([]chan int, 10)for i := 0; i < 10; i++ {chs[i] = make(chan int)go Count(chs[i])}for _, ch := range chs {<-ch} }運(yùn)行結(jié)果:
Counting Counting Counting Counting Counting Counting在這個(gè)例子中,我們定義了一個(gè)包含10個(gè)channel的數(shù)組(名為chs),并把數(shù)組中的每個(gè)channel分配給10個(gè)不同的goroutine。在每個(gè)goroutine的Add()函數(shù)完成后,我們通過(guò)ch <- 1語(yǔ)句向?qū)?yīng)的channel中寫(xiě)入一個(gè)數(shù)據(jù)。在這個(gè)channel被讀取前,這個(gè)操作是阻塞的。在所有的goroutine啟動(dòng)完成后,我們通過(guò)<-ch語(yǔ)句從10個(gè)channel中依次讀取數(shù)據(jù)。在對(duì)應(yīng)的channel寫(xiě)入數(shù)據(jù)前,這個(gè)操作也是阻塞的。這樣,我們就用channel實(shí)現(xiàn)了類(lèi)似鎖的功能,進(jìn)而保證了所有g(shù)oroutine完成后主函數(shù)才返回。
我們?cè)谑褂肎o語(yǔ)言開(kāi)發(fā)時(shí),經(jīng)常會(huì)遇到需要實(shí)現(xiàn)條件等待的場(chǎng)景,這也是channel可以發(fā)揮作用的地方。對(duì)channel的熟練使用,才能真正理解和掌握Go語(yǔ)言并發(fā)編程。
1. 基本語(yǔ)法
一般channel的聲明形式為:
var chanName chan ElementType與一般的變量聲明不同的地方僅僅是在類(lèi)型之前加了chan關(guān)鍵字。 ElementType指定這個(gè)channel所能傳遞的元素類(lèi)型。舉個(gè)例子,我們聲明一個(gè)傳遞類(lèi)型為int的channel:
var ch chan int或者,我們聲明一個(gè)map,元素是bool型的channel:
var m map[string] chan bool上面的語(yǔ)句都是合法的。
定義一個(gè)channel也很簡(jiǎn)單,直接使用內(nèi)置的函數(shù)make()即可:
ch := make(chan int)這就聲明并初始化了一個(gè)int型的名為ch的channel。
在channel的用法中,最常見(jiàn)的包括寫(xiě)入和讀出。將一個(gè)數(shù)據(jù)寫(xiě)入(發(fā)送)至channel的語(yǔ)法很直觀,如下:
ch <- value向channel寫(xiě)入數(shù)據(jù)通常會(huì)導(dǎo)致程序阻塞,直到有其他goroutine從這個(gè)channel中讀取數(shù)據(jù)。從 channel中讀取數(shù)據(jù)的語(yǔ)法是:
value := <-ch如果channel之前沒(méi)有寫(xiě)入數(shù)據(jù),那么從channel中讀取數(shù)據(jù)也會(huì)導(dǎo)致程序阻塞,直到channel中被寫(xiě)入數(shù)據(jù)為止。我們之后還會(huì)提到如何控制channel只接受寫(xiě)或者只允許讀取,即單向channel。
2. select
早在Unix時(shí)代,select機(jī)制就已經(jīng)被引入。通過(guò)調(diào)用select()函數(shù)來(lái)監(jiān)控一系列的文件句柄,一旦其中一個(gè)文件句柄發(fā)生了IO動(dòng)作,該select()調(diào)用就會(huì)被返回。后來(lái)該機(jī)制也被用于實(shí)現(xiàn)高并發(fā)的Socket服務(wù)器程序。
Go語(yǔ)言直接在語(yǔ)言級(jí)別支持select關(guān)鍵字,用于處理異步IO問(wèn)題。
select的用法與switch語(yǔ)言非常類(lèi)似,由select開(kāi)始一個(gè)新的選擇塊,每個(gè)選擇條件由case語(yǔ)句來(lái)描述。與switch語(yǔ)句可以選擇任何可使用相等比較的條件相比, select有比較多的限制,其中最大的一條限制就是每個(gè)case語(yǔ)句里必須是一個(gè)IO操作,大致的結(jié)構(gòu)如下:
select {case <-chan1:// 如果chan1成功讀到數(shù)據(jù),則進(jìn)行該case處理語(yǔ)句case chan2 <- 1:// 如果成功向chan2寫(xiě)入數(shù)據(jù),則進(jìn)行該case處理語(yǔ)句default:// 如果上面都沒(méi)有成功,則進(jìn)入default處理流程 }可以看出, select不像switch,后面并不帶判斷條件,而是直接去查看case語(yǔ)句。每個(gè)case語(yǔ)句都必須是一個(gè)面向channel的操作。比如上面的例子中,第一個(gè)case試圖從chan1讀取一個(gè)數(shù)據(jù)并直接忽略讀到的數(shù)據(jù),而第二個(gè)case則是試圖向chan2中寫(xiě)入一個(gè)整型數(shù)1,如果這兩者都沒(méi)有成功,則到達(dá)default語(yǔ)句。
基于此功能,我們可以實(shí)現(xiàn)一個(gè)有趣的程序:
ch := make(chan int, 1) for {select {case ch <- 0:case ch <- 1:}i := <-chfmt.Println("Value received:", i) }這個(gè)程序?qū)崿F(xiàn)了一個(gè)隨機(jī)向ch中寫(xiě)入一個(gè)0或者1 的過(guò)程,這是個(gè)死循環(huán)。
3. 緩沖機(jī)制
之前的示例都是不帶緩沖的channel,這種做法對(duì)于傳遞單個(gè)數(shù)據(jù)的場(chǎng)景可以接受,但對(duì)于需要持續(xù)傳輸大量數(shù)據(jù)的場(chǎng)景就有些不合適了。接下來(lái)我們介紹如何給channel帶上緩沖,從而達(dá)到消息隊(duì)列的效果。
要?jiǎng)?chuàng)建一個(gè)帶緩沖的channel,其實(shí)也非常容易:
c := make(chan int, 1024)在調(diào)用make()時(shí)將緩沖區(qū)大小作為第二個(gè)參數(shù)傳入即可,比如上面這個(gè)例子就創(chuàng)建了一個(gè)大小為1024的int類(lèi)型channel,即使沒(méi)有讀取方,寫(xiě)入方也可以一直往channel里寫(xiě)入,在緩沖區(qū)被填完之前都不會(huì)阻塞。
從帶緩沖的channel中讀取數(shù)據(jù)可以使用與常規(guī)非緩沖channel完全一致的方法,但我們也可以使用range關(guān)鍵來(lái)實(shí)現(xiàn)更為簡(jiǎn)便的循環(huán)讀取:
for i := range c {fmt.Println("Received:", i) }4. 超時(shí)機(jī)制
在之前對(duì)channel的介紹中,我們完全沒(méi)有提到錯(cuò)誤處理的問(wèn)題,而這個(gè)問(wèn)題顯然是不能被忽略的。在并發(fā)編程的通信過(guò)程中,最需要處理的就是超時(shí)問(wèn)題,即向channel寫(xiě)數(shù)據(jù)時(shí)發(fā)現(xiàn)channel已滿,或者從channel試圖讀取數(shù)據(jù)時(shí)發(fā)現(xiàn)channel為空。如果不正確處理這些情況,很可能會(huì)導(dǎo)致整個(gè)goroutine鎖死。
雖然goroutine是Go語(yǔ)言引入的新概念,但通信鎖死問(wèn)題已經(jīng)存在很長(zhǎng)時(shí)間,在之前的C/C++開(kāi)發(fā)中也存在。操作系統(tǒng)在提供此類(lèi)系統(tǒng)級(jí)通信函數(shù)時(shí)也會(huì)考慮入超時(shí)場(chǎng)景,因此這些方法通常都會(huì)帶一個(gè)獨(dú)立的超時(shí)參數(shù)。超過(guò)設(shè)定的時(shí)間時(shí),仍然沒(méi)有處理完任務(wù),則該方法會(huì)立即終止并返回對(duì)應(yīng)的超時(shí)信息。超時(shí)機(jī)制本身雖然也會(huì)帶來(lái)一些問(wèn)題,比如在運(yùn)行比較快的機(jī)器或者高速的網(wǎng)絡(luò)上運(yùn)行正常的程序,到了慢速的機(jī)器或者網(wǎng)絡(luò)上運(yùn)行就會(huì)出問(wèn)題,從而出現(xiàn)結(jié)果不一致的現(xiàn)象,但從根本上來(lái)說(shuō),解決死鎖問(wèn)題的價(jià)值要遠(yuǎn)大于所帶來(lái)的問(wèn)題。
使用channel時(shí)需要小心,比如對(duì)于以下這個(gè)用法:
i := <-ch不出問(wèn)題的話一切都正常運(yùn)行。但如果出現(xiàn)了一個(gè)錯(cuò)誤情況,即永遠(yuǎn)都沒(méi)有人往ch里寫(xiě)數(shù)據(jù),那么上述這個(gè)讀取動(dòng)作也將永遠(yuǎn)無(wú)法從ch中讀取到數(shù)據(jù), 導(dǎo)致的結(jié)果就是整個(gè)goroutine永遠(yuǎn)阻塞并沒(méi)有挽回的機(jī)會(huì)。如果channel只是被同一個(gè)開(kāi)發(fā)者使用,那樣出問(wèn)題的可能性還低一些。但如果一旦對(duì)外公開(kāi),就必須考慮到最差的情況并對(duì)程序進(jìn)行保護(hù)。
Go語(yǔ)言沒(méi)有提供直接的超時(shí)處理機(jī)制,但我們可以利用select機(jī)制。雖然select機(jī)制不是專(zhuān)為超時(shí)而設(shè)計(jì)的,卻能很方便地解決超時(shí)問(wèn)題。因?yàn)閟elect的特點(diǎn)是只要其中一個(gè)case已經(jīng)完成,程序就會(huì)繼續(xù)往下執(zhí)行,而不會(huì)考慮其他case的情況。
基于此特性,我們來(lái)為channel實(shí)現(xiàn)超時(shí)機(jī)制:
// 首先,我們實(shí)現(xiàn)并執(zhí)行一個(gè)匿名的超時(shí)等待函數(shù) timeout := make(chan bool, 1) go func() {time.Sleep(1e9) // 等待1秒鐘timeout <- true }()// 然后我們把timeout這個(gè)channel利用起來(lái) select {case <-ch:// 從ch中讀取到數(shù)據(jù)case <-timeout:// 一直沒(méi)有從ch中讀取到數(shù)據(jù),但從timeout中讀取到了數(shù)據(jù) }這樣使用select機(jī)制可以避免永久等待的問(wèn)題,因?yàn)槌绦驎?huì)在timeout中獲取到一個(gè)數(shù)據(jù)后繼續(xù)執(zhí)行,無(wú)論對(duì)ch的讀取是否還處于等待狀態(tài),從而達(dá)成1秒超時(shí)的效果。
這種寫(xiě)法看起來(lái)是一個(gè)小技巧,但卻是在Go語(yǔ)言開(kāi)發(fā)中避免channel通信超時(shí)的最有效方法。在實(shí)際的開(kāi)發(fā)過(guò)程中,這種寫(xiě)法也需要被合理利用起來(lái),從而有效地提高代碼質(zhì)量。
5. channel的傳遞
需要注意的是,在Go語(yǔ)言中channel本身也是一個(gè)原生類(lèi)型,與map之類(lèi)的類(lèi)型地位一樣,因此channel本身在定義后也可以通過(guò)channel來(lái)傳遞。
我們可以使用這個(gè)特性來(lái)實(shí)現(xiàn)Linux上非常常見(jiàn)的管道(pipe)特性。管道也是使用非常廣泛的一種設(shè)計(jì)模式,比如在處理數(shù)據(jù)時(shí),我們可以采用管道設(shè)計(jì),這樣可以比較容易以插件的方式增加數(shù)據(jù)的處理流程。
下面我們利用channel可被傳遞的特性來(lái)實(shí)現(xiàn)我們的管道。為了簡(jiǎn)化表達(dá),我們假設(shè)在管道中傳遞的數(shù)據(jù)只是一個(gè)整型數(shù),在實(shí)際的應(yīng)用場(chǎng)景中這通常會(huì)是一個(gè)數(shù)據(jù)塊。
首先限定基本的數(shù)據(jù)結(jié)構(gòu):
type PipeData struct {value inthandler func(int) intnext chan int }然后我們寫(xiě)一個(gè)常規(guī)的處理函數(shù)。我們只要定義一系列PipeData的數(shù)據(jù)結(jié)構(gòu)并一起傳遞給這個(gè)函數(shù),就可以達(dá)到流式處理數(shù)據(jù)的目的:
func handle(queue chan *PipeData) {for data := range queue {data.next <- data.handler(data.value)} }這里我們只給出了大概的樣子,同理,利用channel的這個(gè)可傳遞特性,我們可以實(shí)現(xiàn)非常強(qiáng)大、靈活的系統(tǒng)架構(gòu)。與Go語(yǔ)言接口的非侵入式類(lèi)似,channel的這些特性也可以大大降低開(kāi)發(fā)者的心智成本,用一些比較簡(jiǎn)單卻實(shí)用的方式來(lái)達(dá)成在其他語(yǔ)言中需要使用眾多技巧才能達(dá)成的效果。
6. 單向channel
顧名思義,單向channel只能用于發(fā)送或者接收數(shù)據(jù)。 channel本身必然是同時(shí)支持讀寫(xiě)的,否則根本沒(méi)法用。假如一個(gè)channel真的只能讀,那么肯定只會(huì)是空的,因?yàn)槟銢](méi)機(jī)會(huì)往里面寫(xiě)數(shù)據(jù)。同理,如果一個(gè)channel只允許寫(xiě),即使寫(xiě)進(jìn)去了,也沒(méi)有絲毫意義,因?yàn)闆](méi)有機(jī)會(huì)讀取里面的數(shù)據(jù)。所謂的單向channel概念,其實(shí)只是對(duì)channel的一種使用限制。
我們?cè)趯⒁粋€(gè)channel變量傳遞到一個(gè)函數(shù)時(shí),可以通過(guò)將其指定為單向channel變量,從而限制 該函數(shù)中可 以對(duì)此 channel的操作, 比如只能往 這個(gè) channel寫(xiě),或者只 能從這個(gè)channel讀。
單向channel變量的聲明非常簡(jiǎn)單,如下:
var ch1 chan int // ch1是一個(gè)正常的channel,不是單向的 var ch2 chan<- float64// ch2是單向channel,只用于寫(xiě)float64數(shù)據(jù) var ch3 <-chan int // ch3是單向channel,只用于讀取int數(shù)據(jù)那么單向channel如何初始化呢?之前我們已經(jīng)提到過(guò), channel是一個(gè)原生類(lèi)型,因此不僅支持被傳遞,還支持類(lèi)型轉(zhuǎn)換。只有在介紹了單向channel的概念后,讀者才會(huì)明白類(lèi)型轉(zhuǎn)換對(duì)于channel的意義:就是在單向channel和雙向channel之間進(jìn)行轉(zhuǎn)換。示例如下:
ch4 := make(chan int) ch5 := <-chan int(ch4) // ch5就是一個(gè)單向的讀取channel ch6 := chan<- int(ch4) // ch6 是一個(gè)單向的寫(xiě)入channel基于ch4,我們通過(guò)類(lèi)型轉(zhuǎn)換初始化了兩個(gè)單向channel:單向讀的ch5和單向?qū)懙腸h6。
為什么要做這樣的限制呢?從設(shè)計(jì)的角度考慮,所有的代碼應(yīng)該都遵循“最小權(quán)限原則”,從而避免沒(méi)必要地使用泛濫問(wèn)題,進(jìn)而導(dǎo)致程序失控。寫(xiě)過(guò)C++程序的讀者肯定就會(huì)聯(lián)想起const指針的用法。非const指針具備const指針的所有功能,將一個(gè)指針設(shè)定為const就是明確告訴函數(shù)實(shí)現(xiàn)者不要試圖對(duì)該指針進(jìn)行修改。單向channel也是起到這樣的一種契約作用。
下面我們來(lái)看一下單向channel的用法:
func Parse(ch <-chan int) {for value := range ch {fmt.Println("Parsing value", value)} }除非這個(gè)函數(shù)的實(shí)現(xiàn)者無(wú)恥地使用了類(lèi)型轉(zhuǎn)換,否則這個(gè)函數(shù)就不會(huì)因?yàn)楦鞣N原因而對(duì)ch進(jìn)行寫(xiě),避免在ch中出現(xiàn)非期望的數(shù)據(jù),從而很好地實(shí)踐最小權(quán)限原則。
7. 關(guān)閉channel
關(guān)閉channel非常簡(jiǎn)單,直接使用Go語(yǔ)言內(nèi)置的close()函數(shù)即可:
close(ch)在介紹了如何關(guān)閉channel之后,我們就多了一個(gè)問(wèn)題:如何判斷一個(gè)channel是否已經(jīng)被關(guān)閉?我們可以在讀取的時(shí)候使用多重返回值的方式:
x, ok := <-ch這個(gè)用法與map中的按鍵獲取value的過(guò)程比較類(lèi)似,只需要看第二個(gè)bool返回值即可,如果返回值是false則表示ch已經(jīng)被關(guān)閉。
總結(jié)
以上是生活随笔為你收集整理的go channel 缓冲区最大限制_[Go区块链基础]go channel的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: c语言如何让数组的两个数据调换位置_浅论
- 下一篇: sql的加减乘除运算_小白学sql(一)