天天看點

Golang之并發

        Go從語言層面就支援了并行,這讓C/C++程式猿們淚流滿面

Golang之并發

一、goroutine

        goroutine是Go語言并行設計的核心。goroutine說到底就是線程,但它比線程更小,十幾個goroutine可能展現在底層就是五六個線程,Go語言内部幫你實作了這些goroutine之間的記憶體共享。執行goroutine隻需極少的棧記憶體(大概是4~5KB),當然會根據相應的資料伸縮。是以,可同時運作成千上萬個并發任務。goroutine比thread更易用、更高效、更輕便。

        goroutine是通過Go語言runtime管理的一個線程管理器。goroutine通過go關鍵字實作,其實就是一個普通的函數,類似于線程函數:

        go hello(a, b, c)

        通過關鍵字go就啟動了一個goroutine,舉例說明如下:

        package main

        import (

            "fmt"

            "runtime"

        )

        func say(s string) {

            for i :=0; i

                fmt.Println(s)

            }

        }

        func main() {

            go say("world")    //開一個新的goroutines執行

            say("hello")    //目前goroutines執行

        輸出:

        hello

        world

        上面多個goroutine運作在同一個程序裡面,共享記憶體資料,不過設計上應該遵循:不要通過共享來通信,而要通過通信來共享。

        runtime.Gosched()表示讓CPU把時間片讓給别人,下次某個時候繼續恢複執行該goroutine。

        預設情況下,排程器僅适用單線程,也就是說隻實作了并發。想要發揮多核處理器的并行,需要在程式中顯示調用runtime.GOMAXPROCS(n)告訴排程器同時使用多個線程。GOMAXPROCS設定了同時運作邏輯代碼的系統線程的最大數量,并傳回之前的設定。如果n這篇文章。          

二、channels

        goroutine運作在相同的位址空間,是以,通路共享記憶體必須做好同步。Go語言提供了很好的通信機制channel。channel可以與Unix shell中的雙向管道做類比,通過它發送或者接收值。這些值隻能是特定的類型:channel類型。定義channel時,也需要定義發送到channel的值的類型。注意:必須使用make建立channel。

        ci := make(chan int)

        cs := make(chan string)

        cf := make(chan interface{})

        channel通過操作符

        ch

        v :=

        我們把這些應用到我們的例子中來:

        import "fmt"

        func sum(a []int, c chan int) {

            sum := 0

            for _, v:=range a {

                sum += v

            c

            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 :=

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

        預設情況下,channel接收和發送資料的都是阻塞的,除非另一端已經準備好,這樣就使得goroutines同步變得更加簡單,而不需要顯示的lock。所謂阻塞,就是如果讀取(value :=

三、Buffered channels

        前面介紹了預設的非緩存類型的channel,不過Go語言也允許指定channel的緩沖大小,很簡單,就是channel可以存儲多少元素。ch:=make(chan bool, 4),建立了可以存儲4個元素的bool型channel。在這個channel中,前4個元素可以無阻塞的寫入,當寫入第5個元素時,代碼将會阻塞,直到其它goroutine從channel中讀取一些元素,騰出空間。

        ch := make(chan type, value)

        value == 0 !無緩沖(阻塞)

        value > 0 !緩沖(非阻塞,直到value個元素)

        舉例說明如下(修改相應的value值):

            c := make(chan int, 2)        //修改2為1就報錯,修改2為3可以正常運作

            fmt.Println(

四、Range和Close

        前面例子中,需要讀取兩次c,不是很友善,也可以通過range,像操作slice或者map一樣操作緩存類型的channel。具體請看下例:

        package main

        import (

            "fmt"

        func fibonacci(n int, c chan int) {

            x, y := 1, 1

            for i:+0; i

                c

                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裡面的資料,直到該channel被顯示關閉。從上面代碼可以看出,生産者通過關鍵字close函數顯示關閉channel。關閉channel後就無法再發送任何資料了,消費者可以通過文法v, ok :=

        需要注意的是:應該在生産者的地方關閉channel,而不是消費者的地方去關閉它,這樣容易引起panic。

        另外,channel不像檔案之類需要經常去關閉,隻有當你确實沒有任何資料發送了,或者想顯式的結束range循環之類的操作。

五、Select

        前文介紹的都是隻有一個channel的情況,如果有多個channel,可以通過關鍵字select來監聽channel上的資料流動。

        select預設是阻塞的,隻有當監聽的channel中發送或接收可以進行時才會運作,當多個channel都準備好的時候,select是随機選擇一個執行的。

        import "fmt"

        func fibonacci(c, quit chan int) {

            for {

                select {

                case c

                    x, y = y, x+y

                case

                    fmt.Println("quit")

                    return

                }

            c := make(chan int)

            quit := make(chan int)

            go func() {

                for i:=0; i

                    fmt.Println(

                quit

            } ()

            fibonacci(c, quit)

        在select裡面還有default文法,這類似于switch,default就是當監聽的channel都沒有準備好的時候,預設執行的(select不再阻塞等待channel)。

        select {

        case i :=

            //use i

        default:

            //當c阻塞的時候執行這裡

六、逾時

        有時候會出現goroutine阻塞的情況,可以利用select設定逾時來避免整個程式進入阻塞狀态,具體通過如下方式實作:

            c := make(chan bool)

                for {

                    select {

                        case v :=

                            println(v)

                        case

                        println("timeout")

                        o

                        break

                    }

            }()

七、runtime goroutine

        runtime包中有幾個處理goroutine的函數。

        (1)Goexit

        退出目前執行的goroutine,但是defer函數還會繼續調用。

        (2)Gosched

        讓出目前goroutine的執行權限,排程器安排其它等待的任務運作,并在下次某個時候從該位置恢複執行。

        (3)NumCPU

        傳回CPU核數量。

        (4)NumGoroutine

        傳回正在執行和排隊的任務總數。

        (5)GOMAXPROCS

        用來設定可以運作的CPU核數。