1. 程式人生 > >Go語言學習:Channel

Go語言學習:Channel

本文轉載於http://hustcat.github.io/channel/

“網路,併發”是Go語言的兩大feature。Go語言號稱“網際網路的C語言”,與使用傳統的C語言相比,寫一個Server所使用的程式碼更少,也更簡單。寫一個Server除了網路,另外就是併發,相對python等其它語言,Go對併發支援使得它有更好的效能。

Goroutine和channel是Go在“併發”方面兩個核心feature。

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

Channel宣告:

ChannelType=("chan"|"chan""<-"|"<-"
"chan")ElementType.

例如:

varchchanintvarch1chan<-int//ch1只能寫varch2<-chanint//ch2只能讀

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

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

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

ci:=make(chanint)// unbuffered channel of integerscj:=make(chanint,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(chanint)// Allocate a channel.// Start the sort in a goroutine; when it completes, signal on the channel.
gofunc(){list.Sort()c<-1// Send a signal; value does not matter.}()doSomethingForAWhile()<-c// Wait for sort to finigo; discard sent value.

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

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

訊息傳遞

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

funcProducer(queuechan<-int){fori:=0;i<10;i++{queue<-i}}funcConsumer(queue<-chanint){fori:=0;i<10;i++{v:=<-queuefmt.Println("receive:",v)}}funcmain(){queue:=make(chanint,1)goProducer(queue)goConsumer(queue)time.Sleep(1e9)//讓Producer與Consumer完成}

上面的示例在Producer中生成資料,在Consumer中處理資料。

Server程式設計模型

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

funchandle(r*Request){process(r)// May take a long time.}funcServe(queuechan*Request){for{req:=<-queuegohandle(req)// Don't wait for handle to finigo.}}

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

varsem=make(chanint,MaxOutstanding)funchandle(r*Request){sem<-1// Wait for active queue to drain.process(r)// May take a long time.<-sem// Done; enable next request to run.}funcServe(queuechan*Request){for{req:=<-queuegohandle(req)// Don't wait for handle to finigo.}}

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

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

funcServe(queuechan*Request){forreq:=rangequeue{sem<-1gofunc(){process(req)// Buggy; see explanation below.<-sem}()}}

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

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

funcServe(queuechan*Request){forreq:=rangequeue{sem<-1gofunc(r*Request){process(r)<-sem}(req)}}

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

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

funchandle(queuechan*Request){forr:=rangequeue{process(r)}}funcServe(clientRequestschan*Request,quitchanbool){// Start handlersfori:=0;i<MaxOutstanding;i++{gohandle(clientRequests)}<-quit// Wait to be told to exit.}

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

傳遞channel的channel

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

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

主goroutine:

typeRequeststruct{args[]intresultChanchanint}request:=&Request{[]int{3,4,5},make(chanint)}// Send requestclientRequests<-request// Wait for response.fmt.Printf("answer: %d\n",<-request.resultChan)

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

funchandle(queuechan*Request){forreq:=rangequeue{result:=do_something()req.resultChan<-result}}

多個channel

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

c1:=make(chanstring)c2:=make(chanstring)gofunc(){time.Sleep(time.Second*1)c1<-"one"}()gofunc(){time.Sleep(time.Second*2)c2<-"two"}()fori:=0;i<2;i++{select{casemsg1:=<-c1:fmt.Println("received",msg1)casemsg2:=<-c2:fmt.Println("received",msg2)}}

在C中,我們一般都會傳一個超時時間給select函式,go語言中的select沒有該引數。

select語句

select語句可以用於多個channel的讀或者寫。它與switch語句比較類似,只不過select只用於channel。 如果有多個channel可以處理,那麼select隨機選擇一個channel處理:

for{// send random sequence of bits to cselect{casec<-0:// note: no statement, no fallthrough, no folding of casescasec<-1:}}

如果所有channel都不能處理,如果有default語句,則執行default,如果沒有default,則會阻塞,直到有channel可以處理。一個處理nil channel,沒有default的select會永遠阻塞。這常用於daemon程式。

select{}// block forever

考慮如下程式碼:

packagemainimport"fmt"funcmain(){fmt.Println("start")select{}}

上面的程式碼會返回下面的錯誤:

$ go run select1_ex.go 
start
fatal error: all goroutines are asleep - deadlock!

需要改成下面這種方式:

packagemainimport"time"import"fmt"funcmain(){fmt.Println("start")gofunc(){for{time.Sleep(time.Second*1)fmt.Println("do some work")}}()select{}}

超時

由於select本身並不支援超時,我們需要額外的手段來模擬超時:

相關推薦

Go語言學習Channel

本文轉載於http://hustcat.github.io/channel/ “網路,併發”是Go語言的兩大feature。Go語言號稱“網際網路的C語言”,與使用傳統的C語言相比,寫一個Server所使用的程式碼更少,也更簡單。寫一個Server除了網路,另外就是併發

GO語言學習安裝包下載和linux環境變數配置

1.安裝包下載   地址:https://golang.google.cn/dl/   截圖:    2.解壓安裝包   解壓後的檔案如下圖:     把解壓後的檔案放在 、usr/local/下,如下圖      3.配置環境變數   使用root許可權登入

GO語言學習程式碼拆分(包級私有和公共)

1.把一個檔案拆分為多個(同級目錄)   為了便於理解,在此處用例子說明:   在工作區下新建一個資料夾q0,在此資料夾中,   使用命令vim demo0.go,寫入以下程式碼     package main     import "flag"     var name string    

GO語言學習變數的宣告和賦值

在此處學習幾種典型的做法 1.和其他語言一樣的定義方法   var name string   //go定義一個字串   name="jxd";     //賦值   2.型別推斷   var name="jxd"   

GO語言學習型別轉換和別名

1.型別轉換   go語言不允許隱式轉換,所有型別轉換必須顯式宣告,而且轉換隻能發生在兩種相互相容的型別之間。   var ch byte = 97   var a int = ch      //error   var a int =int(ch) 2.別名   t

GO語言學習切片和陣列

1.陣列、切片   陣列的長度是固定的,切片的長度是可變長的     陣列的長度在宣告時就必須給定,並且之後不會再變化,陣列的長度是其型別的一部分,比如[1]string和[2]string 不是相同的陣列型別.     切片可以看作是對陣列的封裝,每一個切片的底層的資料結構一定

GO語言學習container包中的list和ring

1.List   container/list 包   MoveBefore和MoveAfter分別用於把給定的元素移動到一個元素的前面和後面.   MoveToFront和MoveToBack分別用於把給定的元素移動到連結串列的最前端和最後端.   給定的元素都是*Element型別,*Elemen

GO語言學習map官方解釋

This variable m is a map of string keys to int values:var m map[string]intMap types are reference types, like pointers or slices, and so the value of m a

GO語言學習單通道

1.單通道的應用價值   約束其他程式碼行為,約束其他程式碼行為,約束其他程式碼行為     1.函式的引數為單通道   先看如下程式碼:     func sendInt(ch chan <- int){       ch<-rand.Intn(1000)     }

GO語言學習 專案構建 及編譯 初步

一個GO工程中主要包含以下三個目錄: src:原始碼檔案 pkg:包檔案 bin:相關bin檔案 step1: 建立工程資料夾 goproject step2: 在工程資料夾中建立src,pkg,bin資料夾 step3: 在GOPATH中新增projiect路徑    

GO語言學習動態Web

使用Golang中的模板template來實現在HTML中動態Web. 1.網路埠監聽操作:    Web動態頁面要使用http.HandleFunc()而不是http.Handle()    主函式實現程式碼如下: func main() { http.HandleF

Go語言學習01-基本語法

[TOC] 基本語法 === 原始檔構成 --- 最簡單的一個go程式: ```go package main import "fmt" func main() { fmt.Println("Hello, World!") } ``` Go源程式由幾部分構成: 1. `package`

GO語言學習(四)介面與channel併發

一.介面 1.介面 type Humaner interface { //方法,只有宣告,沒有實現,有別的型別實現 sayhi() } type Student struct { name string id int } //Student實現了此方法 func

Go語言學習筆記十一 切片(slice)

操作 容量 方括號 一個 組類型 學習 中學 slice 修改 Go語言學習筆記十一: 切片(slice) 切片這個概念我是從python語言中學到的,當時感覺這個東西真的比較好用。不像java語言寫起來就比較繁瑣。不過我覺得未來java語法也會支持的。 定義切片 切片可以

Go語言學習筆記(五)變數作用域

Go語言變數型別   Go語言變數型別分為三種:區域性變數,全域性變數,形式引數 型別 定義 描述 區域性變數 在函式內定義的變數 作用域只在函式體內 全域性變數 在函式外定義的變數 全域性變數可以在整個包甚至外部包(被匯出後)

Go語言學習筆記10使用 github.com/weilaihui/fdfs_client 連線 fastDFS時,遇到了cannot find package "golang.org/x/的報錯

今天在嘗試使用weilaihui/fdfs_client時,一直無法通過編譯,主要的報錯有兩處: ./github.com/Sirupsen/logrus/terminal_check_notappengine.go:9:2: cannot find package "golang.org/x/

Go語言學習切片(slice)和範圍(range)

// Go 語言切片是對陣列的抽象。切片的單詞來源於可以對其他陣列和切片進行片段擷取 // Go 陣列的長度不可改變,在特定場景中這樣的集合就不太適用, // Go中提供了一種靈活,功能強悍的內建型別切片("動態陣列"), // 與陣列相比切片的長度是不固定的,可以追加

我的Go語言學習之旅二入門初體驗 Hello World

好吧,所有的程式設計師們都已經習慣了,學習任何一門語言,我們都會以Hello World例項開始我們的學習,我也不例外。先來一個簡單的例子 開啟編輯器 (可以用記事本,我已經習慣 Notepad++了)輸入以下內容 package main import

Go語言學習指標與傳遞到函式的指標

package main import ( "fmt" ) const MAX int = 3 func main() { var a int = 20 var ip *int /* 宣告

Go語言學習——channel的死鎖其實沒那麼複雜

1 為什麼會有通道   協程(goroutine)算是Go的一大新特性,也正是這個大殺器讓Go為很多路人駐足欣賞,讓信徒們為之歡呼津津樂道。   協程的使用也很簡單,在Go中使用關鍵字“go“後面跟上要執行的函式即表示新啟動一個協程中執行功能程式碼。 func main()