Go基礎學習六之並發concurrency

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

Go程式設計語言:支援並發、記憶體回收的編譯型系統級程式設計語言!Go語言從語言層面上就支援了並發,這與其他語言大不一樣,不像以前我們要用Thread庫 來建立線程,還要用安全執行緒的隊列庫來共用資料。

一、並發concurrency

1.基本概念

Go能處理高並發的根本原因在於執行go協程只需極少的棧記憶體(大概4~5KB),並且能根據需要動態增長和縮減佔用的資源。

Go的高並發其實是由goroutine實現的,goroutine是由官方實現的超級“線程池”。

簡單而言,goroutine就是一段代碼,一個函數入口,以及在堆上為其分配的一個堆棧。所以它非常廉價,我們可以很輕鬆的建立上萬個goroutine,但它們並不是被作業系統所調度執行,而是通過系統的線程來多路派遣這些函數的執行,使得每個用go關鍵字執行的函數可以運行成為一個單位協程。當一個協程阻塞的時候,調度器就會自動把其他協程安排到另外的線程中去執行,從而實現了程式無等待並行化運行。而且調度的開銷非常小,一顆CPU調度的規模不下於每秒百萬次,這使得在程式中能夠建立大量的goroutine,實現高並發的同時,依舊能保持高效能。

Goroutine 奉行通過通訊來共用記憶體,而不是共用記憶體來通訊。

2.區別並發和並行

這裡涉及到一個問題,很多同學搞不清楚並發與並行的區別,這裡我根據我根據知乎上這個問題某位網友的例子,我覺得很好:

  • 你吃飯吃到一半,電話來了,你一直到吃完了以後才去接,這就說明你不支援並發也不支援並行
  • 你吃飯吃到一半,電話來了,你停了下來接了電話,接完後繼續吃飯,這說明你支援並發
  • 你吃飯吃到一半,電話來了,你一邊打電話一邊吃飯,這說明你支援並行

並發:你有處理多個任務的能力,不一定同時(一個CPU輪流)

並行:有同時處理多個任務的能力(多個CPU同時)

並發和並行都可以是很多個線程,就看這些線程能不能同時被(多個)CPU執行,可以說明是並行,並發是多個線程被一個CPU輪流切換著執行。

並發不是並行:Concurrency Is Not Parallelism
並發主要由切換時間片來實現“同時”運行,而並行則是直接利用多核實現多線程的運行,但Go可以設定使用核心數,以發揮多核電腦的能力。

並發時,由於cpu執行的太快了,不停地來回切換,讓人以為是同時進行。
並發比並行更優秀,充分的利用了CPU。

3.通道Channel

  • Channelgoroutine 溝通的橋樑,大都是阻塞同步的
  • 通過 make 建立,close 關閉
  • Channel 是參考型別
  • 可以使用 for range 來迭代不斷操作channel
  • 可以設定單向或雙向通道
  • 可以設定緩衝大小,在未被填滿前不會發生阻塞

4.Select

  • 可處理一個或多個channel的發送與接收
  • 同時有多個可用的channel時按隨機排序處理
  • 可用空的 select 來阻塞 main 函數
  • 可設定逾時

二、並發樣本

1.未使用channel關鍵字

package mainimport (    "fmt"    "time")  func main() {    go Go()      // 使用go關鍵字就可執行goroutine    time.Sleep(2 * time.Second)    fmt.Println("Hey ...!")}func Go() {    fmt.Println("Go ...!")    }

列印:

➜  src go run myfirstgo/concurrency.goGo ...!Hey ...!

2.使用channel關鍵字

package mainimport (    "fmt")  func main() {    // 建立channel    c := make(chan bool)    // 執行到此處會阻塞    go func() {        fmt.Println("Go ...!")        c <- true    }()    // 將true的東西存到channel當中,然後讀出來結束運行,即通知main函數,我這裡運行完成了,可以結束了    <-c        fmt.Println("Hey ...!")}

列印:

➜  src go run myfirstgo/concurrency.goGo ...!Hey ...!

3.range樣本

func main() {    // 建立channel    c := make(chan bool)    // 執行到此處會阻塞    go func() {        fmt.Println("Go ...!")        c <- true        close(c)    }()   for v := range c {         fmt.Println(v)   }        fmt.Println("Hey ...!")}

列印:

➜  src go run myfirstgo/concurrency.goGo ...!trueHey ...!

4.多核

package mainimport (    "fmt"    "runtime")  // 當使用單線程執行時,會按部就班,按照順序1,2,3,4執行下去// 當使用多個CPU核心數時,任務分配是不定的,func main() {    // 使用多核    runtime.GOMAXPROCS(runtime.NumCPU())    c := make(chan bool, 10)    for i := 0; i < 10; i++ {        go Go(c, i)    }    <-c}func Go(c chan bool, index int) {    a := 1    for i := 0; i < 100000000; i++ {        a += i    }    fmt.Println(index, a)    if index == 9 {        c <- true    }}

運行多次的執行結果:

➜  src go run myfirstgo/concurrency.go3 49999999500000010 49999999500000011 49999999500000019 4999999950000001➜  src go run myfirstgo/concurrency.go2 49999999500000011 49999999500000013 49999999500000019 4999999950000001➜  src go run myfirstgo/concurrency.go9 4999999950000001➜  src go run myfirstgo/concurrency.go2 49999999500000019 4999999950000001

當使用多個CPU核心數(runtime.GOMAXPROCS)時,任務分配是不定的,所以會出現上邊的結果。

這裡有兩種解決方案:
第一種:
設定一個緩衝長度為10的channel

package mainimport (    "fmt"    "runtime")  // 當使用單線程執行時,會按部就班,按照順序1,2,3,4執行下去// 當使用多個CPU核心數時,任務分配是不定的,func main() {    // 使用多核    runtime.GOMAXPROCS(runtime.NumCPU())    c := make(chan bool, 10)    for i := 0; i < 10; i++ {        go Go(c, i)    }    // 設定一個緩衝長度為10 的channel    for i := 0; i < 10; i++ {        <-c    }    }func Go(c chan bool, index int) {    a := 1    for i := 0; i < 100000000; i++ {        a += i    }    fmt.Println(index, a)    c <- true}

列印:

➜  src go run myfirstgo/concurrency.go1 49999999500000012 49999999500000019 49999999500000013 49999999500000017 49999999500000010 49999999500000018 49999999500000016 49999999500000014 49999999500000015 4999999950000001➜  src go run myfirstgo/concurrency.go0 49999999500000019 49999999500000015 49999999500000011 49999999500000017 49999999500000012 49999999500000018 49999999500000013 49999999500000016 49999999500000014 4999999950000001

第二種:
不是通過channel解決的,而是通過sync包解決的,它有一個waitGroup,可以建立一個工作群組,可以新增工作,每完成一個任務,就標記完成Done,main函數的主要作用就是判斷是否所有的任務都完成了,如果都完成了,則退出程式。

package mainimport (    "fmt"    "runtime"    "sync")  // 當使用單線程執行時,會按部就班,按照順序1,2,3,4執行下去// 當使用多個CPU核心數時,任務分配是不定的,func main() {    // 使用多核    runtime.GOMAXPROCS(runtime.NumCPU())    // sync     wg := sync.WaitGroup{}    wg.Add(10)    for i := 0; i < 10; i++ {        go Go(&wg, i)    }    wg.Wait()    }func Go(wg *sync.WaitGroup, index int) {    a := 1    for i := 0; i < 100000000; i++ {        a += i    }    fmt.Println(index, a)        wg.Done()}

列印:

➜  src go run myfirstgo/concurrency.go1 49999999500000015 49999999500000017 49999999500000019 49999999500000016 49999999500000018 49999999500000010 49999999500000012 49999999500000013 49999999500000014 4999999950000001➜  src go run myfirstgo/concurrency.go1 49999999500000019 49999999500000012 49999999500000013 49999999500000014 49999999500000015 49999999500000010 49999999500000016 49999999500000017 49999999500000018 4999999950000001

相關文章:
golang語言並發與並行——goroutine和channel的詳細理解(一)
golang的goroutine調度機制

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.