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