Golang 併發編程實踐

人是一種高併發的物種,細品。golang

初識

對 Go 語言的第一印象就是其原生地支持併發編程,並且使用的是協程,比線程更加輕量。編程

ybk93cwmg3y51.jpg

關於進程、線程和協程的區別

  • 進程是「程序執行的一個實例」 ,擔當分配系統資源的實體。進程建立必須分配一個完整的獨立地址空間。進程切換隻發生在內核態。
  • 線程:線程是進程的一個執行流,獨立執行它本身的程序代碼,是程序執行流的最小單元,是處理器調度和分派的基本單位。一個進程能夠有一個或多個線程。
  • 協程:協程不是進程或線程,其執行過程更相似於子例程,或者說不帶返回值的函數調用。在語言級別能夠建立併發協程,而後編寫代碼去進行管理。Go 將這一步承包下來,使協程併發運行成本更低。

Go 實現最簡單的併發

for i := 0; i < 10; i++ {
    go func(n int) {
        fmt.Println(n)
    }(i)
}

項目實踐

最近有個項目須要同時調用多個 job,並要等待這些 job 完成以後才能往下執行。併發

串行執行 job

最開始,咱們擁有一個執行 job 的方法,而且串行執行全部的 job:函數

func buildJob(name string) {
    ...
}

buildJob("A")
buildJob("B")
buildJob("C")

並行執行 job

由於全部 job 是能夠併發執行的,這樣就不用必須等待上一個 job 執行完成後,才能繼續執行其餘 job。咱們可使用 Go 語言的關鍵字 go 來快速啓用一個 goroutine,下面咱們將併發地執行三個 job:高併發

go buildJob("A")
go buildJob("B")
go buildJob("C")

等待全部 job 完成

怎樣才能知道每一個 job 是否已經完成,這裏可使用 channel 進行通訊,並使用 select 檢查執行結果:post

func buildJob(ch chan error, name string) {
    var err error
    
    ... // build job
    
    ch <- err // finnaly, send the result into channel
}

func build() error {
    jobCount := 3
    errCh := make(err chan error, jobCount)
    defer close(errCh) // 關閉 channel

    go buildJob(errCh, "A")
    go buildJob(errCh, "B")
    go buildJob(errCh, "C")

    for {
        select {
        case err := <-ch:
            if err != nil {
                return err
            }
        }
        
        jobCount--
        if jobCount <= 0 {
            break
        }
    }
    
    return nil
}

發現問題

當 job A 執行失敗時,build 方法會 return err 退出,並執行 close(errCh)。但是此時另外兩個 job B 和 C 可能還沒執行完成,同時也會把結果發給 errCh,但因爲這個時候 errCh 已經被關閉了,會致使程序退出 panic: send on closed channel優化

優化代碼

在給 channel 發送數據的時候,可使用接收數據的第二個值判斷 channel 是否關閉:ui

func buildJob(ch chan error, name string) {
    var err error
    
    ... // build job
    
    if _, ok := <-ch; !ok {
        return
    }
    ch <- err // finnaly, send the result into channel
}

func build() error {
    jobCount := 3
    errCh := make(err chan error, jobCount)
    defer close(errCh) // 關閉 channel

    go buildJob(errCh, "A")
    go buildJob(errCh, "B")
    go buildJob(errCh, "C")

    for {
        select {
        case err := <-ch:
            if err != nil {
                return err
            }
        }
        
        jobCount--
        if jobCount <= 0 {
            break
        }
    }
    
    return nil
}

總結

Go 併發編程看似只須要一個關鍵字 go 就能夠跑起來一個 goroutine,但真正實踐中,仍是有須要問題須要去處理的。spa

原文連接:https://k8scat.com/posts/code-with-golang-concurrency/線程

相關文章
相關標籤/搜索