在 Go 語言中管理 Concurrency 的三種方式

2020-08-28 Go語言中文網

相信大家踏入 Go 語言的世界,肯定是被強大的並發(Concurrency)所吸引,Go 語言用最簡單的關鍵字go就可以將任務丟到後臺處理,但是開發者怎麼有效率的控制並發,這是入門 Go 語言必學的技能,本章會介紹幾種方式來帶大家認識並發,而這三種方式分別對應到三個不同的名詞:WaitGroup,Channel,及 Context。下面用簡單的範例帶大家了解。

WaitGroup

先來了解有什麼情境需要使用到 WaitGroup,假設您有兩臺機器需要同時上傳最新的代碼,兩臺機器分別上傳完成後,才能執行最後的重啟步驟。就像是把一個工作同時拆成好幾份同時一起做,可以減少時間,但是最後需要等到全部做完,才能執行下一步,這時候就需要用到 WaitGroup 才能做到。

package mainimport (    &34;    &34;)func main() {    var wg sync.WaitGroup    i := 0    wg.Add(3) //task count wait to do    go func() {        defer wg.Done() // finish task1        fmt.Println(&34;)        i++    }()    go func() {        defer wg.Done() // finish task2        fmt.Println(&34;)        i++    }()    go func() {        defer wg.Done() // finish task3        fmt.Println(&34;)        i++    }()    wg.Wait() // wait for tasks to be done    fmt.Println(&34;)    fmt.Println(i)}

Channel

另外一種實際的案例就是,我們需要主動通知一個 Goroutine 進行停止的動作。換句話說,當 App 啟動時,會在後臺跑一些監控程序,而當整個 App 需要停止前,需要發個 Notification 給後臺的監控程序,將其先停止,這時候就需要用到 Channel 來通知。看下下面這個例子:

package mainimport (    &34;    &34;)func main() {    exit := make(chan bool)    go func() {        for {            select {            case <-exit:                fmt.Println(&34;)                return            case <-time.After(2 * time.Second):                fmt.Println(&34;)            }        }    }()    time.Sleep(5 * time.Second)    fmt.Println(&34;)    exit <- true //keep main goroutine alive    time.Sleep(5 * time.Second)}

上面的例子可以發現,用了一個 Gogourtine 和 Channel 來控制。可以想像當後臺有無數個 Goroutine 的時候,我們就需要用多個 Channel 才能進行控制,也許 Goroutine 內又會產生 Goroutine,開發者這時候就會發現已經無法單純使用 Channel 來控制多個 Goroutine 了。這時候解決方式會是傳遞 Context。

Context

大家可以想像,今天有一個後臺任務 A,A 任務又產生了 B 任務,B 任務又產生了 C 任務,也就是可以按照此模式一直產生下去,假設中途我們需要停止 A 任務,而 A 又必須告訴 B 及 C 要一起停止,這時候通過 context 方式是最快的了。

package mainimport (    &34;    &34;    &34;)func foo(ctx context.Context, name string) {    go bar(ctx, name) // A calls B    for {        select {        case <-ctx.Done():            fmt.Println(name, &34;)            return        case <-time.After(1 * time.Second):            fmt.Println(name, &34;)        }    }}func bar(ctx context.Context, name string) {    for {        select {        case <-ctx.Done():            fmt.Println(name, &34;)            return        case <-time.After(2 * time.Second):            fmt.Println(name, &34;)        }    }}func main() {    ctx, cancel := context.WithCancel(context.Background())    go foo(ctx, &34;)    fmt.Println(&34;)    time.Sleep(5 * time.Second)    cancel() //mock client exit, and pass the signal, ctx.Done() gets the signal  time.Sleep(3 * time.Second)    time.Sleep(3 * time.Second)}

package mainimport (    &34;    &34;    &34;)func foo(ctx context.Context, name string) {    go bar(ctx, name) // A calls B    for {        select {        case <-ctx.Done():            fmt.Println(name, &34;)            return        case <-time.After(1 * time.Second):            fmt.Println(name, &34;)        }    }}func bar(ctx context.Context, name string) {    for {        select {        case <-ctx.Done():            fmt.Println(name, &34;)            return        case <-time.After(2 * time.Second):            fmt.Println(name, &34;)        }    }}func main() {    ctx, cancel := context.WithCancel(context.Background())    go foo(ctx, &34;)    fmt.Println(&34;)    time.Sleep(5 * time.Second)    cancel() //mock client exit, and pass the signal, ctx.Done() gets the signal  time.Sleep(3 * time.Second)    time.Sleep(3 * time.Second)}

大家可以把 context 想成是一個 controller,可以隨時控制不確定個數的 Goroutine,由上往下,只要宣告context.WithCancel後,再任意時間點都可以通過cancel()來停止整個後臺服務。實際案例會用在當 App 需要重新啟動時,要先通知全部 goroutine 停止,正常停止後,才會重新啟動 App。

總結

根據不同的情境跟狀況來選擇不同的方式,做一個總結:

  • WaitGroup:需要將單一個工作分解成多個子任務,等到全部完成後,才能進行下一步,這時候用 WaitGroup 最適合了
  • Channel + Select:Channel 只能用在比較單純的 Goroutine 情況下,如果要管理多個 Goroutine,建議還是 走 context 會比較適合
  • Context:如果您想一次控制全部的 Goroutine,相信用 context 會是最適合不過的,當然 context 不只有這特性,詳細可以參考『用 10 分鐘了解 Go 語言 context package 使用場景及介紹』


作者:AppleBOY

原文連結:https://blog.wu-boy.com/2020/08/three-ways-to-manage-concurrency-in-go/

相關焦點

  • Go語言最酷的一些東西
    然後,也可以聲明結構(就像在C中一樣)。 這對於在編譯時檢測錯誤很有幫助。 哦,順便說一句,Go是一種編譯語言。Go代碼編譯非常快! 這是創建者試圖改進的有關C和C ++的關鍵方面之一,他們做到了! 此外,由於代碼直接編譯為機器代碼,因此執行時間非常快。 這也使可執行文件高度可移植到具有相同平臺的其他計算機上。Go有接口。
  • 「Go 語言教程」 Go語言結構
    1 目錄和源碼首先我門看目錄和源碼,從之前的Go 語言教程我們知道,Go語言有工程目錄,和GOPATH環境變量對應,工程目錄結構有bin 存放編譯後的可執行文件src 存放實現源碼,go get工具獲取的web上的模塊包都會放到這個目錄下,並有對應的目錄結構pkg 存放編譯後的庫文件(分不同平臺)Go語言的源碼文件格式為.go格式。
  • 「Go 語言教程」Go語言函數說明
    我們知道在程序設計中,模塊設計是最小的設計單元,模塊設計可以對應面相對象設計中的類設計,也可以對應到函數(方法)設計。編程中很多時候函數作為模塊設計的最小單元。函數設計也有很多方法和規定,以及設計原則。那麼go語言的函數都是怎麼樣子的,都有些什麼原則和要求呢,那麼怎麼做好函數方法設計呢,就讓我們一起來學習學習。
  • 老王學習go語言——3.Go語言基礎 -第一個go程序
    >這裡有三種文件類型分別是 Package Source File,Command Source File和Test Source File隨便選個類型在SRC目錄下新建hello.go然後build參數)來聲明一個函數,由於語言本身支持多返回值,所以函數聲明當中沒有返回值類型的聲明而defaultHandler函數功能其實也很簡單,就是列印hello以及後面的參數,這種佔位輸出方式,做過編程的應該都能看懂。
  • go語言中執行命令的幾種方式
    go語言用來執行一個系統的命令相對python來說還是有點複雜的,執行命令是一個非常常見的需求,如調用一個系統命令只執行命令,不要輸出結果執行命令並且要獲取到輸出結果阻塞和異步的執行以下以ping www.baidu.com 為例依次執行一下各種命令,主要使用標準庫中的os/exec在執行命令的時候,我們主要使用的是os/exec包主的Cmd結構體方法,Cmd的結構體定義如下 Cmd結構體定義
  • 基於GO語言實現web客服即時通訊與客服管理系統GO-FLY
    go-fly基於GO語言實現的web客服即時通訊與客服管理系統。非常適合給自己的網站增加在線客服功能,代碼簡單也適合學習。中間件實現無狀態的jwt登陸認證3.資料庫實現的rbac權限配合gin中間件實現權限控制4.通過cobra進行命令行參數解析和執行對應的功能5.使用go modoule解決依賴問題6.使用swagger實現文檔展示7.使用go-imap實現郵件的列表展示和讀取8
  • go| 使用go連接k8s集群的三種方式
    /kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir")var clientset *kubernetes.Clientset // 也可以手動指定config文件的絕對路徑func ConnFromOutside
  • [Go 語言教程] Go 語言簡介
    Go 語言教程1 Go 語言介紹Go 即Golang,是Google公司2009年11月正式對外公開的一門程式語言。Go是靜態強類型語言,是區別於解析型語言的編譯型語言。解析型語言——原始碼是先翻譯為中間代碼,然後由解析器對代碼進行解釋執行。編譯型語言——原始碼編譯生成機器語言,然後由機器直接執行機器碼即可執行。
  • 理解真實世界中 Go 的並發 BUG
    有幾個學生研究歸納了go編程中的並發bugs,發表了一篇(英文)論文:《Understanding Real-World Concurrency Bugs in Go》。為你下載好了 PDF,關注公眾號 Go語言中文網,回復 gostudy 獲取。
  • Go並發原理
    其實就是Java或者C++等語言中的多線程開發。另外一種是Go語言特有的,也是Go語言推薦的:CSP(communicating sequential processes)並發模型。非常典型的方式就是,在訪問共享數據(例如數組、Map、或者某個結構體或對象)的時候,通過鎖來訪問,因此,在很多時候,衍生出一種方便操作的數據結構,叫做「線程安全的數據結構」。例如Java提供的包」java.util.concurrent」中的數據結構。Go中也實現了傳統的線程並發模型。
  • 跟光磊學Go語言-Go語言概述與開發環境搭建
    Go語言簡介Go語言的發展歷史Go的三位創始人想要解決Google公司在軟體開發中遇到的一些困難和挑戰多核硬體架構的廣泛應用簡單:C語言37個關鍵字,C++11多達84個關鍵字,Go只有25個關鍵字,對於複雜任務,例如並發和內存管理,go語言提供了並發支持以及垃圾回收機制。
  • 一次使用 Go 語言編寫腳本的經歷
    本文介紹了我如何嘗試使用 Go 語言進行腳本編程的經歷。文中我將討論 Go 腳本的必要性,我們預期的表現以及可能的實現方式。在討論過程中,我講深入探討腳本、Shell 和 Shebang。最終,我們將會討論讓 Go 腳本工作的解決方案。為什麼 Go 語言適合編寫腳本?
  • GO語言入門
    GO 命令源碼文件1)命令源碼文件定義:命令源碼文件是程序的運行入口,如果一個源碼文件聲明屬於main包,並且包含一個無參數聲明的main函數,那麼它就是命令源碼文件2)命令源碼文件接收參數的包:GO語言標準庫中有一個代碼包flag專門用於接收和解析程序參數A. flag.StringVar()flag.StringVar
  • AI 領域,Go語言可能很快會取代 Python?
    快進到 2019 年,它成為開發人員中第二受歡迎的語言。Python 成為機器學習和數據科學開發人員的首選語言。在接下來的幾年中,Python 在這些領域的主導地位無疑會持續。但是與更新的語言相比,它具有一些嚴重的缺點。對於 21 實際 20 年代的開發者來說,這可能是一個障礙。
  • go-admin 開源後臺管理系統
    go-admin是一個go語言開發的後臺管理系統,該系統以角色為基礎的權限管理設計(RBAC),完成了系統管理模塊功能的開發(其他示例模塊後續加上),採用前後端分離實現方式,服務端基於go開源gin框架,前端開源框架vue-element-admin。使用Swagger 2.0自動生成API文檔。
  • Go goroutine理解
    Go語言最大的特色就是從語言層面支持並發(Goroutine),Goroutine是Go中最基本的執行單元。事實上每一個Go程序至少有一個Goroutine:主Goroutine。當程序啟動時,它會自動創建。
  • Go語言和Java、Python等其他語言的對比分析
    這是因為Go提供了軟體生命周期(開發、測試、部署、維護等等)的各個環節的工具,如go tool、gofmt、go test。三、對比其他語言Go的很多語言特性借鑑與它的三個祖先:C,Pascal和CSP。
  • go語言在使用中有哪些好處呢
    隨著網際網路的不斷發展,程式語言的種類也越來越多。每個程式語言都具有不同的優缺點,那麼在選擇使用程式語言的時候應該了解什麼內容呢?其實最重要的就是程式語言的優缺點。下面給大家介紹go語言的優點和好處,在使用語言時做出正確的選擇。
  • 從源碼開始分析Go語言的切片
    go 裡面很多內置的方法我們是不能直接找到對應的實現函數的,所以我們通過這個 go tool compile 工具來看一下,就知道了。上面代碼的第5、6行,就是 make slice 的實現,也就是說調用的函數就是 runtime 包中的 makeslice 函數。接下來的7、8行就是 appen 的具體實現,同樣是 runtime 包,growslice 函數。
  • go語言學習總結(五十)Uber Go 語言編程規範
    相信很多人前兩天都看到 Uber 在 github 上面開源的 Go 語言編程規範了,原文在這裡:https://github.com/uber-go/guide/blob/master/style.md 。我們今天就來簡單了解一下國外大廠都是如何來寫代碼的。行文倉促,錯誤之處,多多指正。另外如果覺得還不錯,也歡迎分享給更多的人。1.