深入學習golang channel

2021-09-07 20:21:48 字數 4455 閱讀 2056

「網路,併發」是go語言的兩大feature。go語言號稱「網際網路的c語言」,與使用傳統的c語言相比,寫乙個server所使用的**更少,也更簡單。寫乙個server除了網路,另外就是併發,相對python等其它語言,go對併發支援使得它有更好的效能。

goroutine和channel是go在「併發」方面兩個核心feature。

channel是goroutine之間進行通訊的一種方式,它與unix中的管道類似。

channel宣告:

channeltype = ( "chan" | "chan" "<-" | "<-" "chan" ) elementtype .

例如:

var ch chan int

var ch1 chan<- int //ch1只能寫

var ch2 <-chan int //ch2只能讀

channel是型別相關的,也就是乙個channel只能傳遞一種型別。例如,上面的ch只能傳遞int。

在go語言中,有4種引用型別:slice,map,channel,inte***ce。

slice,map,channel一般都通過make進行初始化:

ci := make(chan int) // unbuffered channel of integers

cj := make(chan int, 0) // unbuffered channel of integers

cs := make(chan *os.file, 100) // buffered channel of pointers to files

建立channel時可以提供乙個可選的整型引數,用於設定該channel的緩衝區大小。該值預設為0,用來構建預設的「無緩衝channel」,也稱為「同步channel」。

channel作為goroutine間的一種通訊機制,與作業系統的其它通訊機制類似,一般有兩個目的:同步,或者傳遞訊息。

c := make(chan int)  // allocate a channel.

// start the sort in a goroutine; when it completes, signal on the channel.

go func() ()

dosomethingforawhile()

<-c // wait for sort to finish; discard sent value.

上面的示例中,在子goroutine中進行排序操作,主goroutine可以做一些別的事情,然後等待子goroutine完成排序。

接收方會一直阻塞直到有資料到來。如果channel是無緩衝的,傳送方會一直阻塞直到接收方將資料取出。如果channel帶有緩衝區,傳送方會一直阻塞直到資料被拷貝到緩衝區;如果緩衝區已滿,則傳送方只能在接收方取走資料後才能從阻塞狀態恢復。

我們來模擬一下經典的生產者-消費者模型。

func producer (queue chan<- int)} 

func consumer( queue <-chan int)}

func main()

上面的示例在producer中生成資料,在consumer中處理資料。

在server程式設計,一種常用的模型:主線程接收請求,然後將請求分發給工作執行緒,工作執行緒完成請求處理。用go來實現,如下:

func handle(r *request) 

func serve(queue chan *request)

}

一般來說,server的處理能力不是無限的,所以,有必要限制執行緒(或者goroutine)的數量。在c/c++程式設計中,我們一般通過訊號量來實現,在go中,我們可以通過channel達到同樣的效果:

var sem = make(chan int, maxoutstanding)

func handle(r *request)

func serve(queue chan *request)

}

我們通過引入sem channel,限制了同時最多只有maxoutstanding個goroutine執行。但是,上面的做法,只是限制了執行的goroutine的數量,並沒有限制goroutine的生成數量。如果請求到來的速度過快,會導致產生大量的goroutine,這會導致系統資源消耗完全。

為此,我們有必要限制goroutine的建立數量:

func serve(queue chan *request) ()

}}

上面的**看似簡單清晰,但在go中,卻有乙個問題。go語言中的迴圈變數每次迭代中是重用的,更直接的說就是req在所有的子goroutine中是共享的,從變數的作用域角度來說,變數req對於所有的goroutine,是全域性的。

這個問題屬於語言實現的範疇,在c語言中,你不應該將乙個區域性變數傳遞給另外乙個執行緒去處理。有很多解決方法,這裡有乙個討論。從個人角度來說,我更傾向下面這種方式:

func serve(queue chan *request) (req)

}}

至少,這樣的**不會讓乙個go的初學者不會迷糊,另外,從變數的作用域角度,也更符合常理一些。

在實際的c/c++程式設計中,我們傾向於工作執行緒在一開始就建立好,而且執行緒的數量也是固定的。在go中,我們也可以這樣做:

func handle(queue chan *request) } 

func serve(clientrequests chan *request, quit chan bool)

<-quit // wait to be told to exit.

}

開始就啟動固定數量的handle goroutine,每個goroutine都直接從channel中讀取請求。這種寫法比較簡單,但是不知道有沒有「驚群」問題?有待後續分析goroutine的實現。

channel作為go語言的一種原生型別,自然可以通過channel進行傳遞。通過channel傳遞channel,可以非常簡單優美的解決一些實際中的問題。

在上一節中,我們主goroutine通過channel將請求傳遞給工作goroutine。同樣,我們也可以通過channel將處理結果返回給主goroutine。

主goroutine:

type request struct 

request := &request, make(chan int)}

// send request

clientrequests <- request

// wait for response.

fmt.printf("answer: %d\n", <-request.resultchan)

主goroutine將請求發給request channel,然後等待result channel。子goroutine完成處理後,將結果寫到result channel。

func handle(queue chan *request) 

}

在實際程式設計中,經常會遇到在乙個goroutine中處理多個channel的情況。我們不可能阻塞在兩個channel,這時就該select場了。與c語言中的select可以監控多個fd一樣,go語言中select可以等待多個channel。

c1 := make(chan string)

c2 := make(chan string)

go func() ()

go func() ()

for i := 0; i < 2; i++

}

在c中,我們一般都會傳乙個超時時間給select函式,go語言中的select沒有該引數,相當於超時時間為0。

UIApplication深入學習

新建乙個任意型別的ios應用工程,加入我們在class prefix輸入是tc,我們可以看到工程中生成乙個類 在main函式中,autoreleasepool 函式中 說明 當應用程式將要入非活動狀態執行,在此期間,應用程式不接收訊息或事件。比如來 了。說明 當應用程式入活動狀態執行,這個剛好跟上面...

深入學習CSS

什麼是css?在之前的這篇文章中已經介紹了初步的介紹,詳細請看 div加css進一步講解了css中的內容,先總結如下圖 其實在實際開發中,我們通常採用是外部樣式的匯入,這樣做的好處是對於很對有同樣設計樣式的頁面可以實現樣式的共享,這樣我們不僅僅可以節省了大量的時間,並且也方便我們可以靈活的呼叫的樣式...

block深入學習

block的宣告和使用看上一節就行了。本章主要講block內部的實現過程及原理。block的定義和函式指標非常相似 對比一下 block定義 void someblock 函式指標定義 void functionpionter void functionname 當然區別還是有的,block的返回型...