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()