九 29
bigwhite技術(shù)志 Actor, BestPractice, Blog, Blogger, Channel, Concurrent, container, CSP, docker, github, Go, Golang, Google, GopherCon, LXC, Opensource, Programmer, TIOBE, 內(nèi)存模型, 博客, 容器, 工作, 布道師, 并發(fā), 開源, 思考, 感悟, 程序員, 編程 暫無評論
在進入正式內(nèi)容前,我這里先順便轉(zhuǎn)發(fā)一則消息,那就是Golang 1.3.2已經(jīng)正式發(fā)布了。國內(nèi)的 Go這門語言也許你還不甚了解,甚至是完全不知道,這也有情可原,畢竟Go在TIOBE編程語言排行榜上位列30開外。但近期使用Golang 實現(xiàn)的一殺手級應(yīng)用 Docker你卻不該不知道。docker目前火得是一塌糊涂啊。你去國內(nèi)外各大技術(shù)站點用眼輕瞥一下,如 果沒有涉及到“docker”字樣新聞的站點建 議你以后就不要再去訪問了^_^。Docker是啥、怎么用以及基礎(chǔ)實踐可以參加國內(nèi)一位仁兄的經(jīng)驗之作:《 Docker – 從入門到實踐》。 據(jù)我了解,目前國內(nèi)試水Go語言開發(fā)后臺系統(tǒng)的大公司與初創(chuàng)公司日益增多,比如七牛、京東、小米,盛大,金山,東軟,搜狗等,在這里我們可以看到一些公司的Go語言應(yīng)用列表,并且目前這個列表似乎依舊在豐富中。國內(nèi)Go語言的推廣與布道也再穩(wěn)步推進中,不過目前來看多以Go入 門與基礎(chǔ)為主題,Go idioms、tips或Best Practice的Share并不多見,想必國內(nèi)的先行者、布道師們還在韜光養(yǎng)晦,積攢經(jīng)驗,等到時機來臨再厚積薄發(fā)。另外國內(nèi)似乎還沒有一個針對Go的 布道平臺,比如Golang技術(shù)大會之類的的平臺。 在國外,雖然Go也剛剛起步,但在Golang share的廣度和深度方面顯然更進一步。Go的國際會議目前還不多,除了Golang老東家Google在自己的各種大會上留給Golang展示自己的 機會外,由 Gopher Academy 發(fā)起的GopherCon 會議也于今年第一次舉行,并放出諸多高質(zhì)量資料,在這里可以下載。歐洲的Go語言大會.dotgo也即將開幕,估計后續(xù)這兩個大會將撐起Golang技術(shù)分享 的旗幟。 言歸正傳,這里要寫的東西并非原創(chuàng),自己的Go僅僅算是入門級別,工程經(jīng)驗、Best Practice等還談不上有多少,因此這里主要是針對GopherCon2014上的“舶來品”的學(xué)習(xí)心得。來自CloudFlare的工程師John Graham-Cumming談了關(guān)于 Channel的實踐經(jīng)驗,這里針對其分享的內(nèi)容,記錄一些學(xué)習(xí)體會和理解,并結(jié)合一些外延知識,也可以算是一種學(xué)習(xí)筆記吧,僅供參考。 一、Golang并發(fā)基礎(chǔ)理論 Golang在并發(fā)設(shè)計方面參考了C.A.R Hoare的CSP,即Communicating Sequential Processes并發(fā)模型理論。但就像John Graham-Cumming所說的那樣,多數(shù)Golang程序員或愛好者僅僅停留在“知道”這一層次,理解CSP理論的并不多,畢竟多數(shù)程序員是搞工程 的。不過要想系統(tǒng)學(xué)習(xí)CSP的人可以從這里下載到CSP論文的最新版本。 維基百科中概要羅列了CSP模型與另外一種并發(fā)模型Actor模型的區(qū)別: Actor模型廣義上講與CSP模型很相似。但兩種模型就提供的原語而言,又有一些根本上的不同之處: 二、Go Channel基本操作語法 Go Channel的基本操作語法如下: c := make(chan bool) //創(chuàng)建一個無緩沖的bool型Channel? 不帶緩沖的Channel兼具通信和同步兩種特性,頗受青睞。 三、Channel用作信號(Signal)的場景 1、等待一個事件(Event) 等待一個事件,有時候通過close一個Channel就足夠了。例如: //testwaitevent1.go import "fmt" func main() { 這里main goroutine通過"<-c"來等待sub goroutine中的“完成事件”,sub goroutine通過close channel促發(fā)這一事件。當(dāng)然也可以通過向Channel寫入一個bool值的方式來作為事件通知。main goroutine在channel c上沒有任何數(shù)據(jù)可讀的情況下會阻塞等待。 關(guān)于輸出結(jié)果: 根據(jù)《Go memory model》中關(guān)于close channel與recv from channel的order的定義:The closing of a channel happens before a receive that returns a zero value because the channel is closed. 我們可以很容易判斷出上面程序的輸出結(jié)果: Begin doing something! 如果將close(c)換成c<-true,則根據(jù)《Go memory model》中的定義:A receive from an unbuffered channel happens before the send on that channel completes. 2、協(xié)同多個Goroutines 同上,close channel還可以用于協(xié)同多個Goroutines,比如下面這個例子,我們創(chuàng)建了100個Worker Goroutine,這些Goroutine在被創(chuàng)建出來后都阻塞在"<-start"上,直到我們在main goroutine中給出開工的信號:"close(start)",這些goroutines才開始真正的并發(fā)運行起來。 //testwaitevent2.go import "fmt" func worker(start chan bool, index int) { func main() { 3、Select 【select的基本操作】 select { case y, ok := <- someOtherchan: case outputChan <- z: default: 【慣用法:for/select】 我們在使用select時很少只是對其進行一次evaluation,我們常常將其與for {}結(jié)合在一起使用,并選擇適當(dāng)時機從for{}中退出。 for { case y, ok := <- someOtherchan: case outputChan <- z: default: 【終結(jié)workers】 下面是一個常見的終結(jié)sub worker goroutines的方法,每個worker goroutine通過select監(jiān)視一個die channel來及時獲取main goroutine的退出通知。 //testterminateworker1.go import ( func worker(die chan bool, index int) { func main() { for i := 1; i <= 100; i++ { time.Sleep(time.Second * 5) 【終結(jié)驗證】 有時候終結(jié)一個worker后,main goroutine想確認(rèn)worker routine是否真正退出了,可采用下面這種方法: //testterminateworker2.go import ( func worker(die chan bool) { func main() { go worker(die) die <- true 【關(guān)閉的Channel永遠不會阻塞】 下面演示在一個已經(jīng)關(guān)閉了的channel上讀寫的結(jié)果: //testoperateonclosedchannel.go import "fmt" func main() { x, ok := <-cb ci := make(chan int) cb <- true $go run testoperateonclosedchannel.go 可以看到在一個已經(jīng)close的unbuffered channel上執(zhí)行讀操作,回返回channel對應(yīng)類型的零值,比如bool型channel返回false,int型channel返回0。但向close的channel寫則會觸發(fā)panic。不過無論讀寫都不會導(dǎo)致阻塞。 【關(guān)閉帶緩存的channel】 將unbuffered channel換成buffered channel會怎樣?我們看下面例子: //testclosedbufferedchannel.go import "fmt" func main() { c <- 1 $go run testclosedbufferedchannel.go 可以看出帶緩沖的channel略有不同。盡管已經(jīng)close了,但我們依舊可以從中讀出關(guān)閉前寫入的3個值。第四次讀取時,則會返回該channel類型的零值。向這類channel寫入操作也會觸發(fā)panic。 【range】 Golang中的range常常和channel并肩作戰(zhàn),它被用來從channel中讀取所有值。下面是一個簡單的實例: //testrange.go import "fmt" func generator(strings chan string) { func main() { 四、隱藏狀態(tài) 下面通過一個例子來演示一下channel如何用來隱藏狀態(tài): 1、例子:唯一的ID服務(wù) //testuniqueid.go import "fmt" func newUniqueIDService() <-chan string { $ go run testuniqueid.go newUniqueIDService通過一個channel與main goroutine關(guān)聯(lián),main goroutine無需知道uniqueid實現(xiàn)的細(xì)節(jié)以及當(dāng)前狀態(tài),只需通過channel獲得最新id即可。 五、默認(rèn)情況 我想這里John Graham-Cumming主要是想告訴我們select的default分支的實踐用法。 1、select for non-blocking receive idle:= make(chan []byte, 5) //用一個帶緩沖的channel構(gòu)造一個簡單的隊列 select { 2、select for non-blocking send idle:= make(chan []byte, 5) //用一個帶緩沖的channel構(gòu)造一個簡單的隊列 select { } 六、Nil Channels 1、nil channels阻塞 對一個沒有初始化的channel進行讀寫操作都將發(fā)生阻塞,例子如下: package main func main() { $go run testnilchannel.go package main func main() { $go run testnilchannel.go 2、nil channel在select中很有用 看下面這個例子: //testnilchannel_bad.go import "fmt" func main() { go func() { for { 我們原本期望程序交替輸出5和7兩個數(shù)字,但實際的輸出結(jié)果卻是: 5 再仔細(xì)分析代碼,原來select每次按case順序evaluate: 我們利用nil channel來改進這個程序,以實現(xiàn)我們的意圖,代碼如下: //testnilchannel.go import "fmt" func main() { go func() { for { $go run testnilchannel.go 可以看出:通過將已經(jīng)關(guān)閉的channel置為nil,下次select將會阻塞在該channel上,使得select繼續(xù)下面的分支evaluation。 七、Timers 1、超時機制Timeout 帶超時機制的select是常規(guī)的tip,下面是示例代碼,實現(xiàn)30s的超時select: func worker(start chan bool) { 2、心跳HeartBeart 與timeout實現(xiàn)類似,下面是一個簡單的心跳select實現(xiàn): func worker(start chan bool) { 2014, bigwhite. 版權(quán)所有. Related posts:
– CSP模型處理過程是匿名的,而Actor模型中的Actor則具有身份標(biāo)識。
– CSP模型的消息傳遞在收發(fā)消息進程間包含了一個交會點,即發(fā)送方只能在接收方準(zhǔn)備好接收消息時才能發(fā)送消息。相反,actor模型中的消息傳遞是異步 的,即消息的發(fā)送和接收無需在同一時間進行,發(fā)送方可以在接收方準(zhǔn)備好接收消息前將消息發(fā)送出去。這兩種方案可以認(rèn)為是彼此對偶的。在某種意義下,基于交 會點的系統(tǒng)可以通過構(gòu)造帶緩沖的通信的方式來模擬異步消息系統(tǒng)。而異步系統(tǒng)可以通過構(gòu)造帶消息/應(yīng)答協(xié)議的方式來同步發(fā)送方和接收方來模擬交會點似的通信 方式。
– CSP使用顯式的Channel用于消息傳遞,而Actor模型則將消息發(fā)送給命名的目的Actor。這兩種方法可以被認(rèn)為是對偶的。某種意義下,進程可 以從一個實際上擁有身份標(biāo)識的channel接收消息,而通過將actors構(gòu)造成類Channel的行為模式也可以打破actors之間的名字耦合。
c <- x //向一個Channel發(fā)送一個值
<- c //從一個Channel中接收一個值
x = <- c //從Channel c接收一個值并將其存儲到x中
x, ok = <- c //從Channel接收一個值,如果channel關(guān)閉了或沒有數(shù)據(jù),那么ok將被置為false
package main
fmt.Println("Begin doing something!")
c := make(chan bool)
go func() {
fmt.Println("Doing something…")
close(c)
}()
<-c
fmt.Println("Done!")
}
Doing something…
Done!
"<-c"要先于"c<-true"完成,但也不影響日志的輸出順序,輸出結(jié)果仍為上面三行。
package main
<-start
fmt.Println("This is Worker:", index)
}
start := make(chan bool)
for i := 1; i <= 100; i++ {
go worker(start, i)
}
close(start)
select {} //deadlock we expected
}
select是Go語言特有的操作,使用select我們可以同時在多個channel上進行發(fā)送/接收操作。下面是select的基本操作。
case x := <- somechan:
// … 使用x進行一些操作
// … 使用y進行一些操作,
// 檢查ok值判斷someOtherchan是否已經(jīng)關(guān)閉
// … z值被成功發(fā)送到Channel上時
// … 上面case均無法通信時,執(zhí)行此分支
}
select {
case x := <- somechan:
// … 使用x進行一些操作
// … 使用y進行一些操作,
// 檢查ok值判斷someOtherchan是否已經(jīng)關(guān)閉
// … z值被成功發(fā)送到Channel上時
// … 上面case均無法通信時,執(zhí)行此分支
}
}
package main
"fmt"
"time"
)
fmt.Println("Begin: This is Worker:", index)
for {
select {
//case xx:
//做事的分支
case <-die:
fmt.Println("Done: This is Worker:", index)
return
}
}
}
die := make(chan bool)
go worker(die, i)
}
close(die)
select {} //deadlock we expected
}
package main
"fmt"
//"time"
)
fmt.Println("Begin: This is Worker")
for {
select {
//case xx:
//做事的分支
case <-die:
fmt.Println("Done: This is Worker")
die <- true
return
}
}
}
die := make(chan bool)
<-die
fmt.Println("Worker goroutine has been terminated")
}
package main
cb := make(chan bool)
close(cb)
x := <-cb
fmt.Printf("%#v\n", x)
fmt.Printf("%#v %#v\n", x, ok)
close(ci)
y := <-ci
fmt.Printf("%#v\n", y)
}
false
false false
0
panic: runtime error: send on closed channel
package main
c := make(chan int, 3)
c <- 15
c <- 34
c <- 65
close(c)
fmt.Printf("%d\n", <-c)
fmt.Printf("%d\n", <-c)
fmt.Printf("%d\n", <-c)
fmt.Printf("%d\n", <-c)
}
15
34
65
0
panic: runtime error: send on closed channel
package main
strings <- "Five hour's New York jet lag"
strings <- "and Cayce Pollard wakes in Camden Town"
strings <- "to the dire and ever-decreasing circles"
strings <- "of disrupted circadian rhythm."
close(strings)
}
strings := make(chan string)
go generator(strings)
for s := range strings {
fmt.Printf("%s\n", s)
}
fmt.Printf("\n")
}
package main
id := make(chan string)
go func() {
var counter int64 = 0
for {
id <- fmt.Sprintf("%x", counter)
counter += 1
}
}()
return id
}
func main() {
id := newUniqueIDService()
for i := 0; i < 10; i++ {
fmt.Println(<-id)
}
}
0
1
2
3
4
5
6
7
8
9
case b = <-idle:? //嘗試從idle隊列中讀取
…
default: //隊列空,分配一個新的buffer
makes += 1
b = make([]byte, size)
}
case idle <- b: //嘗試向隊列中插入一個buffer
//…
default: //隊列滿?
var c chan int
<-c
}
fatal error: all goroutines are asleep – deadlock!
var c chan int
c <- 1
}
fatal error: all goroutines are asleep – deadlock!
package main
import "time"
var c1, c2 chan int = make(chan int), make(chan int)
go func() {
time.Sleep(time.Second * 5)
c1 <- 5
close(c1)
}()
time.Sleep(time.Second * 7)
c2 <- 7
close(c2)
}()
select {
case x := <-c1:
fmt.Println(x)
case x := <-c2:
fmt.Println(x)
}
}
fmt.Println("over")
}
0
0
0
… … 0死循環(huán)
– 前5s,select一直阻塞;
– 第5s,c1返回一個5后被close了,“case x := <-c1”這個分支返回,select輸出5,并重新select
– 下一輪select又從“case x := <-c1”這個分支開始evaluate,由于c1被close,按照前面的知識,close的channel不會阻塞,我們會讀出這個 channel對應(yīng)類型的零值,這里就是0;select再次輸出0;這時即便c2有值返回,程序也不會走到c2這個分支
– 依次類推,程序無限循環(huán)的輸出0
package main
import "time"
var c1, c2 chan int = make(chan int), make(chan int)
go func() {
time.Sleep(time.Second * 5)
c1 <- 5
close(c1)
}()
time.Sleep(time.Second * 7)
c2 <- 7
close(c2)
}()
select {
case x, ok := <-c1:
if !ok {
c1 = nil
} else {
fmt.Println(x)
}
case x, ok := <-c2:
if !ok {
c2 = nil
} else {
fmt.Println(x)
}
}
if c1 == nil && c2 == nil {
break
}
}
fmt.Println("over")
}
5
7
over
timeout := time.After(30 * time.Second)
for {
select {
// … do some stuff
case <- timeout:
return
}
}
}
heartbeat := time.Tick(30 * time.Second)
for {
select {
// … do some stuff
case <- heartbeat:
//… do heartbeat stuff
}
}
}
聯(lián)系客服