GO——學習筆記(九):并發

上篇:GO——學習筆記(八)

下篇:GO——學習筆記(十)

參考:

https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/02.7.md

示例代碼——go_8

https://github.com/jiutianbian/golang-learning/blob/master/go_8/main.go

并發

goroutine

大多數語言實現并發,通常通過多線程來實現,golang也類似,但有所不同,具體如下:goroutine是Go并行設計的核心,goroutine是協程,他的粒度要比線程小,可以認為是輕量級的線程,具體什么是協程,請看此網址 https://www.zhihu.com/question/20862617

執行goroutine只需極少的棧內存,可同時運行成千上萬個并發任務。goroutine比thread更易用、更高效、更輕便。

goroutine 中通過 go 關鍵字來實現,代碼如下

func testgo() {
    go sayhelloMan() //通過go來調用協程執行方法
    go sayhelloWomen()

    time.Sleep(time.Millisecond) //由于goroutine是協程,所以這邊如果不加主線程等待時間,main函數會直接結束,不會等待goroutine執行完畢

    fmt.Println("結束")
}

func sayhelloMan() {
    for i := 0; i < 5; i++ {
        fmt.Println("你好,大兄弟")
        time.Sleep(10)
    }
}

func sayhelloWomen() {
    for i := 0; i < 5; i++ {
        fmt.Println("你好,大妹子")
        time.Sleep(10)
    }
}

請注意time.Sleep(time.Millisecond),這段代碼,如果不加入這段線程阻塞的方法,那么main函數將會很快執行完便退出了,新開的協程將不會繼續執行。

channel

golang中協程間的通信,通過channel來實現,通過make創建出特定的channel類型的值,來發送和接收數據,channel通過操作符<-來接收和發送數據,代碼如下:

func send(c chan int, i int) {
    fmt.Println("發送消息:", i)
    c <- i //將值塞到channle中,如果recive方法沒有執行,send方法所在線程將阻塞
}

func recive(c chan int) {
    i := <-c
    fmt.Println("接收消息:", i)//將值塞從channle中取出,如果send方法沒有執行,recive所在線程將阻塞
}

func testchannel() {
    ch := make(chan int) //定義一個channle,用來接收傳遞的值

    go send(ch, 10)

    recive(ch)
}

我們上面代碼中定義的channle,是無緩沖的channel,無緩沖的channle中數據的存取都是互斥的。拿上面的例子來說,通過send方法,往ch中存入了一個值,如果recive方法沒有執行從ch取出值的操作,send所在線程將阻塞,知道recive方法調用。且recive方法,從ch取出值的時候,只有當send方法,往ch中存入了一個值的時候,才能執行,否則會一直等待。

當然,如果對無緩沖的channel只有存取值的操作的其中一個的話,都將會出現死鎖,報錯 fatal error: all goroutines are asleep - deadlock!

func testchannel() {
    ch := make(chan int) //定義一個channle,用來接收傳遞的值

    //go send(ch, 10) //如果注釋此行或者下面一行中的其中一行,都將會出現死鎖報錯 fatal error: all goroutines are asleep - deadlock!

    recive(ch) 
}

buffered channel

上面的例子中都是定義的無緩沖的channle,現在定義有緩沖的channle,定義格式如下

ch := make(chan type, value)

其實,無緩沖的channle,是value = 0,當value>1時,就是有緩沖的channle。當讀寫有緩沖的channle時,直到寫滿value個元素才阻塞寫入。

func testbufferchannel() {
    //ch := make(chan int)
    bufferchannel := make(chan int, 1) //定義緩沖為1的channel
    bufferchannel <- 1                 //可以直接往緩沖的channel中存值,只要小于1
    //bufferchannel <- 2 //大于1,會報死鎖的錯誤
    //ch <- 1 //對于無緩沖的channle直接賦值,會報死鎖的錯誤

    fmt.Println(<-bufferchannel)
}

Range和Close

可以通過range像操作slice或者map一樣操作緩存類型的channel

func testrangeandclose() {
    bufferchannel_1 := make(chan int, 10)

    go setNumber(cap(bufferchannel_1), bufferchannel_1)
    for i := range bufferchannel_1 { //通過range取緩沖的channel中的值
        fmt.Println(i)
    }
}

func setNumber(n int, c chan int) {
    for i := 0; i < n; i++ {
        c <- i
    }
    close(c) //通過close關閉channel
}

Select

如果存在多個channel的時候,我們可以通過select監聽channel上的數據流動。

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

func testselect() {
    ch := make(chan int)   //定義一個channle,用來接收傳遞的值
    done := make(chan int) //用來傳遞完畢的標識

    go speaknumber(ch, done)

    for {
        select {
        case i := <-ch: //當ch準備好值時
            fmt.Println("speack:", i)
        case <-done: //當done準備好值時
            fmt.Println("speack:done")
            return
        }
    }
}

func speaknumber(ch chan int, done chan int) {
    for i := 0; i < 10; i++ {
        ch <- i
    }
    done <- 0
}

在select里面還有default語法,select其實就是類似switch的功能,default就是當監聽的channel都沒有準備好的時候,默認執行的(select不再阻塞等待channel)。

select {
case i := <-c:
    // use i
default:
    // 當c阻塞的時候執行這里
}

超時

對于上面描述過阻塞超時的情況,我們可以通過select來設置超時來避免.

func testovertime() {
    ch := make(chan int) //定義一個channle,用來接收傳遞的值

    go saynumber(ch)

    for {
        select {
        case i := <-ch:
            fmt.Println("say:", i)
        case <-time.After(1 * time.Second):
            fmt.Println("say:timeout")
            return
        }
    }
}

func saynumber(ch chan int) {
    for i := 0; i < 10; i++ {
        ch <- i
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 并發基礎 在說Golang的并發編程之前,先認識一下目前并發的幾種實現方式: 1.多進程。操作系統實現的并發模型,...
    睡著別叫醒我閱讀 2,261評論 0 1
  • 能力模型 選擇題 [primary] 下面屬于關鍵字的是()A. funcB. defC. structD. cl...
    _張曉龍_閱讀 24,883評論 14 224
  • 控制并發有三種種經典的方式,一種是通過channel通知實現并發控制 一種是WaitGroup,另外一種就是Con...
    wiseAaron閱讀 10,722評論 4 34
  • Goroutine Go在語言層面對并發編程提供支持,采用輕量級線程(協程)實現。只需要在函數調用語句前添加go關...
    技術學習閱讀 1,546評論 0 0
  • 在我們談論協程(Goroutines)泄漏之前,我們先看看并發編程的概念。并發編程處理程序的并發執行。多個連續流任...
    范彬2017閱讀 6,356評論 0 7