GoLang併發控制(上)

作者:不喜歡夜雨天 鏈接:https://www.jianshu.com/p/23057498e2c3 來源:簡書

在go程序中,最被人所熟知的便是併發特性,一方面有goroutine這類二級線程,對這種不處於用戶態的go程的支持,另一方面便是對併發編程的簡便化,可以快捷穩定的寫出支持併發的程序。

  • 先回顧進程or線程之間的通信方式 inte-process communication(IPC) 其中Go支持的IPC方法有管道、信號和socket。篇(shui)幅(ping)有限,一張圖引入回憶。

ipc圖解.jpg

  • 併發和並行 簡單來講 併發就是可同時發起執行的程序,並行就是可以在支持並行的硬件上執行的併發程序;換句話說,併發程序代表了所有可以實現併發行爲的程序,這是一個比較寬泛的概念,並行程序也只是他的一個子集。 這個問題在知乎筆試中出現過,並行和併發不是一個概念。

複習過過去的基礎知識後,進入主題。

開發go程序,不管是系統性的k8s平臺,還是基於傳統的web開發,都常常使用goroutine來併發處理任務,有時候goroutine之間是相互獨立的,但是也有時候goroutine之間是需要同步和通信的;另一種情況是父goroutine需要控制屬於他的子goroutine。而goroutine的設計機制爲,goroutine退出只能由本身進行控制,不同與傳統的用戶態協程,不允許從外部強制結束該goroutine,除非goroutine奔潰或者main函數結束。目前實現多個goroutine間的同步與通信大致有:

  • 全局共享變量
  • channel管道通信 ---CSP模型
  • context包 ---在1.7版本後引入

全局共享變量: 實現思路爲:

  1. 申明一個全局變量。
  2. 在這個全局變量作用域中,開啓多個go程,多個go程實際上是共享這個全局變量。
  3. 開啓的多個go程實現循環,不斷的監聽這個全局變量的情況,若全局變量屬性觸發一定條件,跳出循環,按串行順序執行到該go程結尾,go程生命週期結束。

代碼示例:

 1package mainimport (    "fmt"
 2    "time")func main() {
 3    running := true
 4    f := func() {        for running { //控制的全局共享變量
 5            fmt.Println("sub proc running...")
 6            time.Sleep(1 * time.Second)
 7        }
 8        fmt.Println("sub proc exit")
 9    }    go f()
10    go f()
11    go f()
12    time.Sleep(2 * time.Second)
13    running = false //全局共享變量改變
14    time.Sleep(3 * time.Second)
15    fmt.Println("main proc exit")
16}
  • 優點:實現簡單,不抽象,直白方便,一個變量即可簡單控制子goroutine的進行。
  • 缺點:
    • 不能適應結構複雜的設計,功能有限,只能適用於子go程中讀,外主程或父go程來寫全局變量,若子go程中進行寫,會出現數據同步問題,需要加鎖解決,不加鎖面對map這類線程不安全的結構會報錯。
    • 還有不適合用於同級的子go程間的通信,全局變量傳遞的信息太少。
    • 還有就是主進程無法等待所有子goroutine退出,因爲這種方式只能是單向通知,所以這種方法只適用於非常簡單的邏輯且併發量不太大的場景。

channel通信

首先解釋golang中的channel:channel是go中的核心部分之一,結構體簡單概括就是一個ring隊列+一個鎖 有興趣的同學可以去研究一下源碼構建。在使用中可以將channel看做管道,通過channel迸發執行的go程之間就可以發送或者接受數據,從而對併發邏輯進行控制。

go的channel的設計是建立在CSP(Communicating Sequential Process),中文可以叫做通信順序進程,是一種併發編程模型,由 Tony Hoare 於 1977 年提出。簡單來說,CSP 模型由併發執行的實體(線程或者進程)所組成,實體之間通過發送消息進行通信,這裏發送消息時使用的就是通道,或者叫 channel。CSP 模型的關鍵是關注 channel,而不關注發送消息的實體。Go 語言實現了 CSP 部分理論,goroutine 對應 CSP 中併發執行的實體,channel 也就對應着 CSP 中的 channel。 也就是說,CSP 描述這樣一種併發模型:多個Process 使用一個 Channel 進行通信, 這個 Channel 連結的 Process 通常是匿名的,消息傳遞通常是同步的(有別於 Actor Model)。

設計思路:

  1. 創建一個sync包中WaitGroup實例 var wg sync.WaitGroup
  2. 創建一個chan,負責控制go程退出
  3. 在每一個go程被創建前,執行註冊. wg.Add(1)
  4. 創建go後,在go程中監聽信號chan能否收到,使用select機制(和io多路複用相似)
  5. runtime主程 直接關閉chan,也可以選擇發送信號量。通知子go程結束循環,結束go程
  6. go程調用 wg.Done()註銷後再退出,所以在進行go程 使用defer,go程退出執行。
  7. wg.Wait() 在註冊的所有信息註銷後才繼續執行下一步。原理是實現一個for死循環,在註冊的值消耗完畢後,跳出死循環。

代碼解釋:

 1package mainimport (    "fmt"
 2    "os"
 3    "os/signal"
 4    "sync"
 5    "syscall"
 6    "time")//執行的go程方法,傳入的chanfunc consumer(stop <-chan bool) {    for {
 7        select {                    //機制類似epoll
 8        case <-stop:
 9            fmt.Println("exit sub goroutine")            return
10        default:
11            fmt.Println("running...")
12            time.Sleep(500 * time.Millisecond)
13        }
14    }
15}
16func main() {
17        stop := make(chan bool)
18        var wg sync.WaitGroup        // Spawn example consumers
19        for i := 0; i < 3; i++ {
20            wg.Add(1)            go func(stop <-chan bool) {
21                defer wg.Done()
22                consumer(stop)
23            }(stop)
24        }
25        close(stop) //直接關閉chan,否則傳遞3次信號量
26        fmt.Println("stopping all jobs!")
27        wg.Wait()
28        fmt.Println("OVER")
29}

channel通信控制基於CSP模型,相比於傳統的線程與鎖併發模型,避免了大量的加鎖解鎖的性能消耗,而又比Actor模型更加靈活,使用Actor模型時,負責通訊的媒介與執行單元是緊耦合的–每個Actor都有一個信箱。而使用CSP模型,channel是第一對象,可以被獨立地創建,寫入和讀出數據,更容易進行擴展。


版權申明:內容來源網絡,版權歸原創者所有。除非無法確認,我們都會標明作者及出處,如有侵權煩請告知,我們會立即刪除並表示歉意。謝謝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章