一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

Golang Channel用法簡(jiǎn)編 | Tony Bai

 icecity1306 2014-10-08

Golang Channel用法簡(jiǎn)編

九 29

bigwhite技術(shù)志 , , , , , , , , , , , , , , , , , , , , , , , , , , , , 暫無(wú)評(píng)論

在進(jìn)入正式內(nèi)容前,我這里先順便轉(zhuǎn)發(fā)一則消息,那就是Golang 1.3.2已經(jīng)正式發(fā)布了。國(guó)內(nèi)的golangtc已經(jīng)鏡像了的安裝包下載頁(yè)面,國(guó)內(nèi)go程序員與愛(ài)好者們可以到"Golang中 國(guó)",即去下載go 1.3.2版本。

Go這門語(yǔ)言也許你還不甚了解,甚至是完全不知道,這也有情可原,畢竟Go在TIOBE編程語(yǔ)言排行榜上位列30開(kāi)外。但近期使用Golang 實(shí)現(xiàn)的一殺手級(jí)應(yīng)用 Docker你卻不該不知道。docker目前火得是一塌糊涂啊。你去國(guó)內(nèi)外各大技術(shù)站點(diǎn)用眼輕瞥一下,如 果沒(méi)有涉及到“docker”字樣新聞的站點(diǎn)建 議你以后就不要再去訪問(wèn)了^_^。Docker是啥、怎么用以及基礎(chǔ)實(shí)踐可以參加國(guó)內(nèi)一位仁兄的經(jīng)驗(yàn)之作:《 Docker – 從入門到實(shí)踐》。

據(jù)我了解,目前國(guó)內(nèi)試水Go語(yǔ)言開(kāi)發(fā)后臺(tái)系統(tǒng)的大公司與初創(chuàng)公司日益增多,比如七牛、京東、小米,盛大,金山,東軟,搜狗等,在這里我們可以看到一些公司的Go語(yǔ)言應(yīng)用列表,并且目前這個(gè)列表似乎依舊在豐富中。國(guó)內(nèi)Go語(yǔ)言的推廣與布道也再穩(wěn)步推進(jìn)中,不過(guò)目前來(lái)看多以Go入 門與基礎(chǔ)為主題,Go idioms、tips或Best Practice的Share并不多見(jiàn),想必國(guó)內(nèi)的先行者、布道師們還在韜光養(yǎng)晦,積攢經(jīng)驗(yàn),等到時(shí)機(jī)來(lái)臨再厚積薄發(fā)。另外國(guó)內(nèi)似乎還沒(méi)有一個(gè)針對(duì)Go的 布道平臺(tái),比如Golang技術(shù)大會(huì)之類的的平臺(tái)。

在國(guó)外,雖然Go也剛剛起步,但在Golang share的廣度和深度方面顯然更進(jìn)一步。Go的國(guó)際會(huì)議目前還不多,除了Golang老東家Google在自己的各種大會(huì)上留給Golang展示自己的 機(jī)會(huì)外,由 Gopher Academy 發(fā)起的GopherCon 會(huì)議也于今年第一次舉行,并放出諸多高質(zhì)量資料,在這里可以下載。歐洲的Go語(yǔ)言大會(huì).dotgo也即將開(kāi)幕,估計(jì)后續(xù)這兩個(gè)大會(huì)將撐起Golang技術(shù)分享 的旗幟。

言歸正傳,這里要寫(xiě)的東西并非原創(chuàng),自己的Go僅僅算是入門級(jí)別,工程經(jīng)驗(yàn)、Best Practice等還談不上有多少,因此這里主要是針對(duì)GopherCon2014上的“舶來(lái)品”的學(xué)習(xí)心得。來(lái)自CloudFlare的工程師John Graham-Cumming談了關(guān)于 Channel的實(shí)踐經(jīng)驗(yàn),這里針對(duì)其分享的內(nèi)容,記錄一些學(xué)習(xí)體會(huì)和理解,并結(jié)合一些外延知識(shí),也可以算是一種學(xué)習(xí)筆記吧,僅供參考。

一、Golang并發(fā)基礎(chǔ)理論

Golang在并發(fā)設(shè)計(jì)方面參考了C.A.R Hoare的CSP,即Communicating Sequential Processes并發(fā)模型理論。但就像John Graham-Cumming所說(shuō)的那樣,多數(shù)Golang程序員或愛(ài)好者僅僅停留在“知道”這一層次,理解CSP理論的并不多,畢竟多數(shù)程序員是搞工程 的。不過(guò)要想系統(tǒng)學(xué)習(xí)CSP的人可以從這里下載到CSP論文的最新版本。

維基百科中概要羅列了CSP模型與另外一種并發(fā)模型Actor模型的區(qū)別:

Actor模型廣義上講與CSP模型很相似。但兩種模型就提供的原語(yǔ)而言,又有一些根本上的不同之處:
    – CSP模型處理過(guò)程是匿名的,而Actor模型中的Actor則具有身份標(biāo)識(shí)。
    – CSP模型的消息傳遞在收發(fā)消息進(jìn)程間包含了一個(gè)交會(huì)點(diǎn),即發(fā)送方只能在接收方準(zhǔn)備好接收消息時(shí)才能發(fā)送消息。相反,actor模型中的消息傳遞是異步 的,即消息的發(fā)送和接收無(wú)需在同一時(shí)間進(jìn)行,發(fā)送方可以在接收方準(zhǔn)備好接收消息前將消息發(fā)送出去。這兩種方案可以認(rèn)為是彼此對(duì)偶的。在某種意義下,基于交 會(huì)點(diǎn)的系統(tǒng)可以通過(guò)構(gòu)造帶緩沖的通信的方式來(lái)模擬異步消息系統(tǒng)。而異步系統(tǒng)可以通過(guò)構(gòu)造帶消息/應(yīng)答協(xié)議的方式來(lái)同步發(fā)送方和接收方來(lái)模擬交會(huì)點(diǎn)似的通信 方式。
    – CSP使用顯式的Channel用于消息傳遞,而Actor模型則將消息發(fā)送給命名的目的Actor。這兩種方法可以被認(rèn)為是對(duì)偶的。某種意義下,進(jìn)程可 以從一個(gè)實(shí)際上擁有身份標(biāo)識(shí)的channel接收消息,而通過(guò)將actors構(gòu)造成類Channel的行為模式也可以打破actors之間的名字耦合。

二、Go Channel基本操作語(yǔ)法

Go Channel的基本操作語(yǔ)法如下:

c := make(chan bool) //創(chuàng)建一個(gè)無(wú)緩沖的bool型Channel?
c <- x        //向一個(gè)Channel發(fā)送一個(gè)值
<- c          //從一個(gè)Channel中接收一個(gè)值
x = <- c      //從Channel c接收一個(gè)值并將其存儲(chǔ)到x中
x, ok = <- c  //從Channel接收一個(gè)值,如果channel關(guān)閉了或沒(méi)有數(shù)據(jù),那么ok將被置為false

不帶緩沖的Channel兼具通信和同步兩種特性,頗受青睞。

三、Channel用作信號(hào)(Signal)的場(chǎng)景

1、等待一個(gè)事件(Event)

等待一個(gè)事件,有時(shí)候通過(guò)close一個(gè)Channel就足夠了。例如:

//testwaitevent1.go
package main

import "fmt"

func main() {
        fmt.Println("Begin doing something!")
        c := make(chan bool)
        go func() {
                fmt.Println("Doing something…")
                close(c)
        }()
        <-c
        fmt.Println("Done!")
}

這里main goroutine通過(guò)"<-c"來(lái)等待sub goroutine中的“完成事件”,sub goroutine通過(guò)close channel促發(fā)這一事件。當(dāng)然也可以通過(guò)向Channel寫(xiě)入一個(gè)bool值的方式來(lái)作為事件通知。main goroutine在channel c上沒(méi)有任何數(shù)據(jù)可讀的情況下會(huì)阻塞等待。

關(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!
Doing something…
Done!

如果將close(c)換成c<-true,則根據(jù)《Go memory model》中的定義:A receive from an unbuffered channel happens before the send on that channel completes.
"<-c"要先于"c<-true"完成,但也不影響日志的輸出順序,輸出結(jié)果仍為上面三行。

2、協(xié)同多個(gè)Goroutines

同上,close channel還可以用于協(xié)同多個(gè)Goroutines,比如下面這個(gè)例子,我們創(chuàng)建了100個(gè)Worker Goroutine,這些Goroutine在被創(chuàng)建出來(lái)后都阻塞在"<-start"上,直到我們?cè)趍ain goroutine中給出開(kāi)工的信號(hào):"close(start)",這些goroutines才開(kāi)始真正的并發(fā)運(yùn)行起來(lái)。

//testwaitevent2.go
package main

import "fmt"

func worker(start chan bool, index int) {
        <-start
        fmt.Println("This is Worker:", index)
}

func main() {
        start := make(chan bool)
        for i := 1; i <= 100; i++ {
                go worker(start, i)
        }
        close(start)
        select {} //deadlock we expected
}

3、Select

【select的基本操作】
select是Go語(yǔ)言特有的操作,使用select我們可以同時(shí)在多個(gè)channel上進(jìn)行發(fā)送/接收操作。下面是select的基本操作。

select {
case x := <- somechan:
    // … 使用x進(jìn)行一些操作

case y, ok := <- someOtherchan:
    // … 使用y進(jìn)行一些操作,
    //
檢查ok值判斷someOtherchan是否已經(jīng)關(guān)閉

case outputChan <- z:
    // … z值被成功發(fā)送到Channel上時(shí)

default:
    // … 上面case均無(wú)法通信時(shí),執(zhí)行此分支
}

【慣用法:for/select】

我們?cè)谑褂胹elect時(shí)很少只是對(duì)其進(jìn)行一次evaluation,我們常常將其與for {}結(jié)合在一起使用,并選擇適當(dāng)時(shí)機(jī)從for{}中退出。

for {
        select {
        case x := <- somechan:
            // … 使用x進(jìn)行一些操作

        case y, ok := <- someOtherchan:
            // … 使用y進(jìn)行一些操作,
            // 檢查ok值判斷someOtherchan是否已經(jīng)關(guān)閉

        case outputChan <- z:
            // … z值被成功發(fā)送到Channel上時(shí)

        default:
            // … 上面case均無(wú)法通信時(shí),執(zhí)行此分支
        }
}

【終結(jié)workers】

下面是一個(gè)常見(jiàn)的終結(jié)sub worker goroutines的方法,每個(gè)worker goroutine通過(guò)select監(jiān)視一個(gè)die channel來(lái)及時(shí)獲取main goroutine的退出通知。

//testterminateworker1.go
package main

import (
    "fmt"
    "time"
)

func worker(die chan bool, index int) {
    fmt.Println("Begin: This is Worker:", index)
    for {
        select {
        //case xx:
            //做事的分支
        case <-die:
            fmt.Println("Done: This is Worker:", index)
            return
        }
    }
}

func main() {
    die := make(chan bool)

    for i := 1; i <= 100; i++ {
        go worker(die, i)
    }

    time.Sleep(time.Second * 5)
    close(die)
    select {}
//deadlock we expected
}

【終結(jié)驗(yàn)證】

有時(shí)候終結(jié)一個(gè)worker后,main goroutine想確認(rèn)worker routine是否真正退出了,可采用下面這種方法:

//testterminateworker2.go
package main

import (
    "fmt"
    //"time"
)

func worker(die chan bool) {
    fmt.Println("Begin: This is Worker")
    for {
        select {
        //case xx:
        //做事的分支
        case <-die:
            fmt.Println("Done: This is Worker")
            die <- true
            return
        }
    }
}

func main() {
    die := make(chan bool)

    go worker(die)

    die <- true
    <-die
    fmt.Println("Worker goroutine has been terminated")
}

【關(guān)閉的Channel永遠(yuǎn)不會(huì)阻塞】

下面演示在一個(gè)已經(jīng)關(guān)閉了的channel上讀寫(xiě)的結(jié)果:

//testoperateonclosedchannel.go
package main

import "fmt"

func main() {
        cb := make(chan bool)
        close(cb)
        x := <-cb
        fmt.Printf("%#v\n", x)

        x, ok := <-cb
        fmt.Printf("%#v %#v\n", x, ok)

        ci := make(chan int)
        close(ci)
        y := <-ci
        fmt.Printf("%#v\n", y)

        cb <- true
}

$go run testoperateonclosedchannel.go
false
false false
0
panic: runtime error: send on closed channel

可以看到在一個(gè)已經(jīng)close的unbuffered channel上執(zhí)行讀操作,回返回channel對(duì)應(yīng)類型的零值,比如bool型channel返回false,int型channel返回0。但向close的channel寫(xiě)則會(huì)觸發(fā)panic。不過(guò)無(wú)論讀寫(xiě)都不會(huì)導(dǎo)致阻塞。

【關(guān)閉帶緩存的channel】

將unbuffered channel換成buffered channel會(huì)怎樣?我們看下面例子:

//testclosedbufferedchannel.go
package main

import "fmt"

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

        c <- 1
}

$go run testclosedbufferedchannel.go
15
34
65
0
panic: runtime error: send on closed channel

可以看出帶緩沖的channel略有不同。盡管已經(jīng)close了,但我們依舊可以從中讀出關(guān)閉前寫(xiě)入的3個(gè)值。第四次讀取時(shí),則會(huì)返回該channel類型的零值。向這類channel寫(xiě)入操作也會(huì)觸發(fā)panic。

【range】

Golang中的range常常和channel并肩作戰(zhàn),它被用來(lái)從channel中讀取所有值。下面是一個(gè)簡(jiǎn)單的實(shí)例:

//testrange.go
package main

import "fmt"

func generator(strings chan string) {
        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)
}

func main() {
        strings := make(chan string)
        go generator(strings)
        for s := range strings {
                fmt.Printf("%s\n", s)
        }
        fmt.Printf("\n")
}

四、隱藏狀態(tài)

下面通過(guò)一個(gè)例子來(lái)演示一下channel如何用來(lái)隱藏狀態(tài):

1、例子:唯一的ID服務(wù)

//testuniqueid.go
package main

import "fmt"

func newUniqueIDService() <-chan string {
        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)
        }
}

$ go run testuniqueid.go
0
1
2
3
4
5
6
7
8
9

newUniqueIDService通過(guò)一個(gè)channel與main goroutine關(guān)聯(lián),main goroutine無(wú)需知道uniqueid實(shí)現(xiàn)的細(xì)節(jié)以及當(dāng)前狀態(tài),只需通過(guò)channel獲得最新id即可。

五、默認(rèn)情況

我想這里John Graham-Cumming主要是想告訴我們select的default分支的實(shí)踐用法。

1、select  for non-blocking receive

idle:= make(chan []byte, 5) //用一個(gè)帶緩沖的channel構(gòu)造一個(gè)簡(jiǎn)單的隊(duì)列

select {
case b = <-idle:? //嘗試從idle隊(duì)列中讀取
    …
default:  //隊(duì)列空,分配一個(gè)新的buffer
        makes += 1
        b = make([]byte, size)
}

2、select for non-blocking send

idle:= make(chan []byte, 5) //用一個(gè)帶緩沖的channel構(gòu)造一個(gè)簡(jiǎn)單的隊(duì)列

select {
case idle <- b: //嘗試向隊(duì)列中插入一個(gè)buffer
        //…
default: //隊(duì)列滿?

}

六、Nil Channels

1、nil channels阻塞

對(duì)一個(gè)沒(méi)有初始化的channel進(jìn)行讀寫(xiě)操作都將發(fā)生阻塞,例子如下:

package main

func main() {
        var c chan int
        <-c
}

$go run testnilchannel.go
fatal error: all goroutines are asleep – deadlock!

package main

func main() {
        var c chan int
        c <- 1
}

$go run testnilchannel.go
fatal error: all goroutines are asleep – deadlock!

2、nil channel在select中很有用

看下面這個(gè)例子:

//testnilchannel_bad.go
package main

import "fmt"
import "time"

func main() {
        var c1, c2 chan int = make(chan int), make(chan int)
        go func() {
                time.Sleep(time.Second * 5)
                c1 <- 5
                close(c1)
        }()

        go func() {
                time.Sleep(time.Second * 7)
                c2 <- 7
                close(c2)
        }()

        for {
                select {
                case x := <-c1:
                        fmt.Println(x)
                case x := <-c2:
                        fmt.Println(x)
                }
        }
        fmt.Println("over")
}

我們?cè)酒谕绦蚪惶孑敵?和7兩個(gè)數(shù)字,但實(shí)際的輸出結(jié)果卻是:

5
0
0
0
… … 0死循環(huán)

再仔細(xì)分析代碼,原來(lái)select每次按case順序evaluate:
    – 前5s,select一直阻塞;
    – 第5s,c1返回一個(gè)5后被close了,“case x := <-c1”這個(gè)分支返回,select輸出5,并重新select
    – 下一輪select又從“case x := <-c1”這個(gè)分支開(kāi)始evaluate,由于c1被close,按照前面的知識(shí),close的channel不會(huì)阻塞,我們會(huì)讀出這個(gè) channel對(duì)應(yīng)類型的零值,這里就是0;select再次輸出0;這時(shí)即便c2有值返回,程序也不會(huì)走到c2這個(gè)分支
    – 依次類推,程序無(wú)限循環(huán)的輸出0

我們利用nil channel來(lái)改進(jìn)這個(gè)程序,以實(shí)現(xiàn)我們的意圖,代碼如下:

//testnilchannel.go
package main

import "fmt"
import "time"

func main() {
        var c1, c2 chan int = make(chan int), make(chan int)
        go func() {
                time.Sleep(time.Second * 5)
                c1 <- 5
                close(c1)
        }()

        go func() {
                time.Sleep(time.Second * 7)
                c2 <- 7
                close(c2)
        }()

        for {
                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")
}

$go run testnilchannel.go
5
7
over

可以看出:通過(guò)將已經(jīng)關(guān)閉的channel置為nil,下次select將會(huì)阻塞在該channel上,使得select繼續(xù)下面的分支evaluation。

七、Timers

1、超時(shí)機(jī)制Timeout

帶超時(shí)機(jī)制的select是常規(guī)的tip,下面是示例代碼,實(shí)現(xiàn)30s的超時(shí)select:

func worker(start chan bool) {
        timeout := time.After(30 * time.Second)
        for {
                select {
                     // … do some stuff
                case <- timeout:
                    return
                }
        }
}

2、心跳HeartBeart

與timeout實(shí)現(xiàn)類似,下面是一個(gè)簡(jiǎn)單的心跳select實(shí)現(xiàn):

func worker(start chan bool) {
        heartbeat := time.Tick(30 * time.Second)
        for {
                select {
                     // … do some stuff
                case <- heartbeat:
                    //… do heartbeat stuff
                }
        }
}

2014, bigwhite. 版權(quán)所有.

Related posts:

  1. Go程序設(shè)計(jì)語(yǔ)言(三)
  2. Go中的系統(tǒng)Signal處理
  3. Go與C語(yǔ)言的互操作
  4. Go程序設(shè)計(jì)語(yǔ)言(一)
  5. Go程序設(shè)計(jì)語(yǔ)言(二)

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    日韩欧美国产高清在线| 国产日韩熟女中文字幕| 丰满少妇被猛烈撞击在线视频 | 精品亚洲av一区二区三区| 午夜精品麻豆视频91| 狠狠做深爱婷婷久久综合| 国产精品午夜福利免费在线| 国产成人午夜福利片片| 亚洲欧美日产综合在线网| 国产精品福利一级久久| 久久国内午夜福利直播| 麻豆精品在线一区二区三区| 婷婷色网视频在线播放| 东京热男人的天堂一二三区| 日韩高清一区二区三区四区| 中国美女草逼一级黄片视频| 少妇熟女亚洲色图av天堂| 日本不卡一区视频欧美| 久久国产亚洲精品成人| 视频一区日韩经典中文字幕| 亚洲专区中文字幕视频| 日韩精品日韩激情日韩综合| 最新午夜福利视频偷拍| 国产一区二区三区成人精品| 欧美日韩校园春色激情偷拍| 亚洲av熟女国产一区二区三区站| 亚洲欧美国产网爆精品| 插进她的身体里在线观看骚| 亚洲国产香蕉视频在线观看| 日韩一级欧美一级久久| 大胆裸体写真一区二区| 精品午夜福利无人区乱码| 99久久人妻精品免费一区| 国产欧美日韩精品一区二| 亚洲一区二区三区av高清| 中文字幕五月婷婷免费| 精品欧美一区二区三久久| 激情国产白嫩美女在线观看| 国产爆操白丝美女在线观看| 日韩国产亚洲欧美另类| 日本特黄特色大片免费观看|