Golang基礎(chǔ)(七) | 并發(fā)

goroutine

goroutine是Go并行設(shè)計(jì)的核心。goroutine說(shuō)到底其實(shí)就是協(xié)程,但是它比線程更小,十幾個(gè)goroutine可能體現(xiàn)在底層就是五六個(gè)線程,Go語(yǔ)言內(nèi)部幫你實(shí)現(xiàn)了這些goroutine之間的內(nèi)存共享瞳步。執(zhí)行g(shù)oroutine只需極少的棧內(nèi)存(大概是4~5KB),當(dāng)然會(huì)根據(jù)相應(yīng)的數(shù)據(jù)伸縮腰奋。也正因?yàn)槿绱说テ穑赏瑫r(shí)運(yùn)行成千上萬(wàn)個(gè)并發(fā)任務(wù)。goroutine比thread更易用劣坊、更高效馏臭、更輕便。
goroutine是通過(guò)Go的runtime管理的一個(gè)線程管理器。goroutine通過(guò)go關(guān)鍵字實(shí)現(xiàn)了括儒,其實(shí)就是一個(gè)普通的函數(shù)绕沈。

下面是通過(guò)關(guān)鍵字go就啟動(dòng)了一個(gè)goroutine

package main

import (
    "fmt"
    "runtime"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        runtime.Gosched()
        fmt.Println(s)
    }
}

func main() {
    go say("world") //開(kāi)一個(gè)新的Goroutines執(zhí)行
    say("hello") //當(dāng)前Goroutines執(zhí)行
}

我們可以看到go關(guān)鍵字很方便的就實(shí)現(xiàn)了并發(fā)編程。 上面的多個(gè)goroutine運(yùn)行在同一個(gè)進(jìn)程里面帮寻,共享內(nèi)存數(shù)據(jù)乍狐,不過(guò)設(shè)計(jì)上我們要遵循:不要通過(guò)共享來(lái)通信,而要通過(guò)通信來(lái)共享固逗。

Go 1.5以前調(diào)度器僅使用單線程浅蚪,也就是說(shuō)只實(shí)現(xiàn)了并發(fā)。想要發(fā)揮多核處理器的并行烫罩,需要在我們的程序中顯式調(diào)用 runtime.GOMAXPROCS(n) 告訴調(diào)度器同時(shí)使用多個(gè)線程惜傲。GOMAXPROCS 設(shè)置了同時(shí)運(yùn)行邏輯代碼的系統(tǒng)線程的最大數(shù)量,并返回之前的設(shè)置贝攒。如果n < 1盗誊,不會(huì)改變當(dāng)前設(shè)置;在Go 1.5將標(biāo)識(shí)并發(fā)系統(tǒng)線程個(gè)數(shù)的runtime.GOMAXPROCS的初始值由1改為了運(yùn)行環(huán)境的CPU核數(shù)隘弊。

channel

goroutine運(yùn)行在相同的地址空間哈踱,因此訪問(wèn)共享內(nèi)存必須做好同步。那么goroutine之間如何進(jìn)行數(shù)據(jù)的通信呢梨熙,Go提供了一個(gè)很好的通信機(jī)制channel开镣。channel可以與Unix shell 中的雙向管道做類(lèi)比:可以通過(guò)它發(fā)送或者接收值。這些值只能是特定的類(lèi)型:channel類(lèi)型咽扇。定義一個(gè)channel時(shí)邪财,也需要定義發(fā)送到channel的值的類(lèi)型。注意质欲,必須使用make 創(chuàng)建channel:

ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})

channel通過(guò)操作符<-來(lái)接收和發(fā)送數(shù)據(jù)

ch <- v    // 發(fā)送v到channel ch.
v := <-ch  // 從ch中接收數(shù)據(jù)卧蜓,并賦值給v

下面是channel使用的完整例子:

package main

import "fmt"

func sum(a []int, c chan int) {
    total := 0
    for _, v := range a {
        total += v
    }
    c <- total  // send total to c
}

func main() {
    a := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(a[:len(a)/2], c)
    go sum(a[len(a)/2:], c)
    x, y := <-c, <-c  // receive from c

    fmt.Println(x, y, x + y)
}

默認(rèn)情況下,channel接收和發(fā)送數(shù)據(jù)都是阻塞的把敞,除非另一端已經(jīng)準(zhǔn)備好,這樣就使得Goroutines同步變的更加的簡(jiǎn)單榨惠,而不需要顯式的lock奋早。所謂阻塞,也就是如果讀仍取(value := <-ch)它將會(huì)被阻塞耽装,直到有數(shù)據(jù)接收。其次期揪,任何發(fā)送(ch<-5)將會(huì)被阻塞掉奄,直到數(shù)據(jù)被讀出。無(wú)緩沖channel經(jīng)常在多個(gè)goroutine之間同步。

Buffered Channels

上面我們介紹了默認(rèn)的非緩存類(lèi)型的channel姓建,不過(guò)Go也允許指定channel的緩沖大小诞仓,很簡(jiǎn)單,就是channel可以存儲(chǔ)多少元素速兔。ch:= make(chan bool, 4)墅拭,創(chuàng)建了可以存儲(chǔ)4個(gè)元素的bool 型channel。在這個(gè)channel 中涣狗,前4個(gè)元素可以無(wú)阻塞的寫(xiě)入谍婉。當(dāng)寫(xiě)入第5個(gè)元素時(shí),代碼將會(huì)阻塞镀钓,直到其他goroutine從channel 中讀取一些元素穗熬,騰出空間。

ch := make(chan type, value)

當(dāng) value = 0 時(shí)丁溅,channel 是無(wú)緩沖阻塞讀寫(xiě)的唤蔗,當(dāng)value > 0 時(shí),channel 有緩沖唧瘾、是非阻塞的措译,直到寫(xiě)滿 value 個(gè)元素才阻塞寫(xiě)入。
我們看一下下面這個(gè)例子饰序,你可以在自己本機(jī)測(cè)試一下领虹,修改相應(yīng)的value值

package main

import "fmt"

func main() {
    c := make(chan int, 2)//修改2為1就報(bào)錯(cuò),修改2為3可以正常運(yùn)行
    c <- 1
    c <- 2
    fmt.Println(<-c)
    fmt.Println(<-c)
}
        //修改為1報(bào)如下的錯(cuò)誤:
        //fatal error: all goroutines are asleep - deadlock!

上面這個(gè)例子中求豫,我們需要讀取兩次c塌衰,這樣不是很方便,Go考慮到了這一點(diǎn)蝠嘉,所以也可以通過(guò)range最疆,像操作slice或者map一樣操作緩存類(lèi)型的channel,請(qǐng)看下面的例子

package main

import (
    "fmt"
)

func fibonacci(n int, c chan int) {
    x, y := 1, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x + y
    }
    close(c)
}

func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
}

for i := range c能夠不斷的讀取channel里面的數(shù)據(jù)蚤告,直到該channel被顯式的關(guān)閉努酸。上面代碼我們看到可以顯式的關(guān)閉channel,生產(chǎn)者通過(guò)內(nèi)置函數(shù)close關(guān)閉channel杜恰。關(guān)閉channel之后就無(wú)法再發(fā)送任何數(shù)據(jù)了获诈,在消費(fèi)方可以通過(guò)語(yǔ)法v, ok := <-ch測(cè)試channel是否被關(guān)閉。如果ok返回false心褐,那么說(shuō)明channel已經(jīng)沒(méi)有任何數(shù)據(jù)并且已經(jīng)被關(guān)閉舔涎。

select

我們上面介紹的都是只有一個(gè)channel的情況,那么如果存在多個(gè)channel的時(shí)候逗爹,我們?cè)撊绾尾僮髂赝鱿樱珿o里面提供了一個(gè)關(guān)鍵字select,通過(guò)select可以監(jiān)聽(tīng)channel上的數(shù)據(jù)流動(dòng)。
select默認(rèn)是阻塞的挟冠,只有當(dāng)監(jiān)聽(tīng)的channel中有發(fā)送或接收可以進(jìn)行時(shí)才會(huì)運(yùn)行于购,當(dāng)多個(gè)channel都準(zhǔn)備好的時(shí)候,select是隨機(jī)的選擇一個(gè)執(zhí)行的圃郊。

package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 1, 1
    for {
        select {
        case c <- x:
            x, y = y, x + y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}

在select里面還有default語(yǔ)法价涝,select其實(shí)就是類(lèi)似switch的功能,default就是當(dāng)監(jiān)聽(tīng)的channel都沒(méi)有準(zhǔn)備好的時(shí)候持舆,默認(rèn)執(zhí)行的(select不再阻塞等待channel)色瘩。

select {
case i := <-c:
    // use i
default:
    // 當(dāng)c阻塞的時(shí)候執(zhí)行這里
}

有時(shí)候會(huì)出現(xiàn)goroutine阻塞的情況,那么我們?nèi)绾伪苊庹麄€(gè)程序進(jìn)入阻塞的情況呢逸寓?我們可以利用select來(lái)設(shè)置超時(shí)居兆,通過(guò)如下的方式實(shí)現(xiàn):

func main() {
    c := make(chan int)
    o := make(chan bool)
    go func() {
        for {
            select {
                case v := <- c:
                    println(v)
                case <- time.After(5 * time.Second):
                    println("timeout")
                    o <- true
                    break
            }
        }
    }()
    <- o
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市竹伸,隨后出現(xiàn)的幾起案子泥栖,更是在濱河造成了極大的恐慌,老刑警劉巖勋篓,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吧享,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡譬嚣,警方通過(guò)查閱死者的電腦和手機(jī)钢颂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)拜银,“玉大人殊鞭,你說(shuō)我怎么就攤上這事∧嵬埃” “怎么了操灿?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)泵督。 經(jīng)常有香客問(wèn)我趾盐,道長(zhǎng),這世上最難降的妖魔是什么小腊? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任救鲤,我火速辦了婚禮,結(jié)果婚禮上溢豆,老公的妹妹穿的比我還像新娘。我一直安慰自己瘸羡,他們只是感情好漩仙,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般队他。 火紅的嫁衣襯著肌膚如雪卷仑。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天麸折,我揣著相機(jī)與錄音锡凝,去河邊找鬼。 笑死垢啼,一個(gè)胖子當(dāng)著我的面吹牛窜锯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播芭析,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼锚扎,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了馁启?” 一聲冷哼從身側(cè)響起驾孔,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惯疙,沒(méi)想到半個(gè)月后翠勉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡霉颠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年对碌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掉分。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡俭缓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出酥郭,到底是詐尸還是另有隱情华坦,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布不从,位于F島的核電站惜姐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏椿息。R本人自食惡果不足惜歹袁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望寝优。 院中可真熱鬧条舔,春花似錦、人聲如沸乏矾。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至凄硼,卻和暖如春铅协,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背摊沉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工狐史, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人说墨。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓骏全,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親婉刀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吟温,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容