Go如何有效控制Goroutine并發(fā)數量

相信大家在學習Go的過程中谓松,都會看到類似這樣一句話:"與傳統(tǒng)的系統(tǒng)級線程和進程相比,協(xié)程的最大優(yōu)勢在于其‘輕量級’,可以輕松創(chuàng)建上百萬個而不會導致系統(tǒng)資源衰竭"咐汞。那是不是意味著我們在開發(fā)過程中概行,可以隨心所欲的調用協(xié)程蠢挡,而不關心它的數量呢?

答案當然是否定的凳忙。我們在開發(fā)過程中业踏,如果不對Goroutine加以控制而進行濫用的話,可能會導致服務程序整體崩潰涧卵。

這里我先模擬一下協(xié)程數量太多的危害:

func main() {
  number := math.MaxInt64
  for i := 0; i < number; i++ {
    go func(i int) {
      // 做一些業(yè)務邏輯處理
      fmt.Printf("go func: %d\n", i)
      time.Sleep(time.Second)
    }(i)
  }
}

如果number是用戶輸入的一個參數勤家,沒有做限制。有些開發(fā)人員會全部丟進去進行循環(huán)柳恐,認為全部都并發(fā)使用Goroutine去做一件事情伐脖,效率比較高。但這樣的話乐设,噩夢般的事情就開始了讼庇,服務器系統(tǒng)資源利用率不斷上漲,到最后程序自動killed伤提。

image.png

通過執(zhí)行top命令查看到該程序占用的CPU巫俺、內存較高。

image.png

為了避免上圖這種情況肿男,下面會簡單的介紹一下Goroutine以及在我們日常開發(fā)中如何控制Goroutine的數量介汹。

一、基本介紹

工欲善其事必先利其器舶沛。先簡單的介紹一下Goroutine嘹承,Goroutine是Go中最基本的執(zhí)行單元。事實上每一個Go程序至少有一個Goroutine:主Goroutine如庭。當程序啟動時叹卷,它會自動創(chuàng)建撼港。

為了更好理解Goroutine,先講一下進程骤竹、線程和協(xié)程的概念帝牡。

進程(process):用戶下達運行程序的命令后,就會產生進程蒙揣。同一程序可產生多個進程(一對多關系)靶溜,以允許同時有多位用戶運行同一程序,卻不會相沖突懒震。進程需要一些資源才能完成工作罩息,如CPU使用時間、存儲器个扰、文件以及I/O設備瓷炮,且為依序逐一進行,也就是每個CPU核心任何時間內僅能運行一項進程递宅。進程的局限是創(chuàng)建娘香、撤銷和切換的開銷比較大。

線程(Thread):有時被稱為輕量級進程(Lightweight Process恐锣,LWP)茅主,是程序執(zhí)行流的最小單元。一個標準的線程由線程ID土榴,當前指令指針(PC)诀姚,寄存器集合和堆棧組成。另外玷禽,線程是進程中的一個實體赫段,是被系統(tǒng)獨立調度和分派的基本單位,線程自己不擁有系統(tǒng)資源矢赁,只擁有一點兒在運行中必不可少的資源糯笙,但它可與同屬一個進程的其它線程共享進程所擁有的全部資源。線程擁有自己獨立的棧和共享的堆撩银,共享堆给涕,不共享棧,線程的切換一般也由操作系統(tǒng)調度额获。

協(xié)程(coroutine):又稱微線程與子例程(或者稱為函數)一樣够庙,協(xié)程(coroutine)也是一種程序組件。相對子例程而言抄邀,協(xié)程更為一般和靈活耘眨,但在實踐中使用沒有子例程那樣廣泛。和線程類似境肾,共享堆剔难,不共享棧胆屿,協(xié)程的切換一般由程序員在代碼中顯式控制。它避免了上下文切換的額外耗費偶宫,兼顧了多線程的優(yōu)點非迹,簡化了高并發(fā)程序的復雜。

Goroutine和其他語言的協(xié)程(coroutine)在使用方式上類似纯趋,但從字面意義上來看不同(一個是Goroutine彻秆,一個是coroutine),再就是協(xié)程是一種協(xié)作任務控制機制结闸,在最簡單的意義上,協(xié)程不是并發(fā)的酒朵,而Goroutine支持并發(fā)的桦锄。因此Goroutine可以理解為一種Go語言的協(xié)程,同時它可以運行在一個或多個線程上蔫耽。

在Go中生成一個Goroutine的方式非常的簡單:只要在函數前面加上go就生成了结耀。

 func number() {
    for i := 0; i < ; i++ {
        fmt.Printf("%d ", i)
    }
}
func main() {
   go number() // 啟動一個goroutine
   number()
}

二、協(xié)程池解決匙铡?

回到開頭的問題图甜,如何控制Goroutine的數量?相信有過開發(fā)經驗的人鳖眼,第一想法是生成協(xié)程池黑毅,通過協(xié)程池控制連接的數量,這樣每次連接都從協(xié)程池里去拿钦讳。在Golang開發(fā)中需要協(xié)程池嗎矿瘦?這里分享下知乎有個相關點贊最高的回答:

顯然不需要,goroutine的初衷就是輕量級的線程愿卒,為的就是讓你隨用隨起缚去,結果你又搞個池子來,這不是脫褲子放屁么琼开?你需要的是限制并發(fā)易结,而協(xié)程池是一種違背了初衷的方法。池化要解決的問題一個是頻繁創(chuàng)建的開銷柜候,另一個是在等待時占用的資源搞动。goroutine 和普通線程相比,創(chuàng)建和調度都不需要進入內核改橘,也就是創(chuàng)建的開銷已經解決了滋尉。同時相比系統(tǒng)線程,內存占用也是輕量的飞主。所以池化技術要解決的問題goroutine 都不存在狮惜,為什么要創(chuàng)建 goroutine pool 呢高诺?如果因為 goroutine 持有資源而要去創(chuàng)建goroutine pool,那只能說明代碼的耦合度較高碾篡,應該為這類資源創(chuàng)建一個goroutine-safe的對象池虱而,而不是把goroutine本身池化。

在我們日常大部分場景下开泽,不需要使用協(xié)程池牡拇。因為Goroutine非常輕量,默認2kb穆律,使用go func()很難成為性能瓶頸惠呼。當然一些極端情況下需要追求性能,可以使用協(xié)程池實現(xiàn)資源的復用峦耘,例如FastHttp使用協(xié)程池性能提高許多剔蹋。

當然現(xiàn)在我們如果需要使用Goroutine池也不需要重復造輪子了,目前github上已經有開源的項目ants來實現(xiàn) Goroutine 池辅髓。ants已經實現(xiàn)了對大規(guī)模 Goroutine 的調度管理泣崩、Goroutine 復用,允許使用者在開發(fā)并發(fā)程序的時候限制 Goroutine 數量洛口,復用資源矫付,達到更高效執(zhí)行任務的效果。

項目地址:https://github.com/panjf2000/ants

三第焰、 通過channel和sync方式限制協(xié)程數量

3.1 Channel

Goroutine運行在相同的地址空間买优,因此訪問共享內存必須做好同步。那么Goroutine之間如何進行數據的通信呢樟遣?Go提供了一個很好的通信機制channel而叼,channel可以與 Unix shell 中的雙向管道做類比:可以通過它發(fā)送或者接收值。這些值只能是特定的類型:channel類型豹悬。定義一個channel時葵陵,也需要定義發(fā)送到channel的值的類型。注意瞻佛,必須使用make創(chuàng)建channel脱篙。

3.2 Sync

Go語言中有一個sync.WaitGroup,WaitGroup 對象內部有一個計數器伤柄,最初從0開始绊困,它有三個方法:Add(), Done(), Wait() 用來控制計數器的數量。下面示例代碼中wg.Wati會阻塞代碼的運行适刀,直到計數器值為0秤朗。

通過Golang自帶的channel和sync,可以實現(xiàn)需求笔喉,下面代碼中通過channel控制Goroutine數量取视。

 package main
import (
  "fmt"
  "sync"
  "time"
)
type Glimit struct {
  n int
  c chan struct{}
}
// initialization Glimit struct
func New(n int) *Glimit {
  return &Glimit{
    n: n,
    c: make(chan struct{}, n),
  }
}
// Run f in a new goroutine but with limit.
func (g *Glimit) Run(f func()) {
  g.c <- struct{}{}
  go func() {
    f()
    <-g.c
  }()
}
var wg = sync.WaitGroup{}
func main() {
  number := 10
  g := New(2)
  for i := 0; i < number; i++ {
    wg.Add(1)
    value :=i
    goFunc := func() {
      // 做一些業(yè)務邏輯處理
      fmt.Printf("go func: %d\n", value)
      time.Sleep(time.Second)
      wg.Done()
    }
    g.Run(goFunc)
  }
  wg.Wait()
}

四硝皂、總結

在文章的開頭通過在服務器模擬Goroutine數量太多導致系統(tǒng)資源上升,提醒大家避免這類問題作谭。當然每個人可根據自己所在的場景選擇最合適的方案稽物,有時候成熟的第三方庫也是個很好的選擇,可以避免重復造輪子折欠。

下面有兩個思考問題贝或,大家可以嘗試著去思考一下。

思考1:為什么我們要使用sync.WaitGroup锐秦?

這里如果我們不使用sync.WaitGroup控制的話咪奖,原因出在當主程序結束時,子協(xié)程也是會被終止掉的酱床。因此剩余的 goroutine 沒來及把值輸出赡艰,程序就已經中斷了

思考2:代碼中channel數據結構為什么定義struct,而不定義成bool這種類型呢斤葱?

因為空結構體變量的內存占用大小為0,而bool類型內存占用大小為1揖闸,這樣可以更加最大化利用我們服務器的內存空間揍堕。

func main(){
  a :=struct{}{}
  b := true
  fmt.Println(unsafe.Sizeof(a))  # println 0
  fmt.Println(unsafe.Sizeof(b))  # println 1
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市汤纸,隨后出現(xiàn)的幾起案子衩茸,更是在濱河造成了極大的恐慌,老刑警劉巖贮泞,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件楞慈,死亡現(xiàn)場離奇詭異,居然都是意外死亡啃擦,警方通過查閱死者的電腦和手機囊蓝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來令蛉,“玉大人聚霜,你說我怎么就攤上這事≈槭澹” “怎么了蝎宇?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長祷安。 經常有香客問我姥芥,道長,這世上最難降的妖魔是什么汇鞭? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任凉唐,我火速辦了婚禮庸追,結果婚禮上,老公的妹妹穿的比我還像新娘熊榛。我一直安慰自己锚国,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布玄坦。 她就那樣靜靜地躺著血筑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪煎楣。 梳的紋絲不亂的頭發(fā)上豺总,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音择懂,去河邊找鬼喻喳。 笑死,一個胖子當著我的面吹牛困曙,可吹牛的內容都是我干的表伦。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼慷丽,長吁一口氣:“原來是場噩夢啊……” “哼蹦哼!你這毒婦竟也來了?” 一聲冷哼從身側響起要糊,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤纲熏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后锄俄,有當地人在樹林里發(fā)現(xiàn)了一具尸體局劲,經...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年奶赠,在試婚紗的時候發(fā)現(xiàn)自己被綠了鱼填。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡毅戈,死狀恐怖剔氏,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情竹祷,我是刑警寧澤谈跛,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站塑陵,受9級特大地震影響感憾,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一阻桅、第九天 我趴在偏房一處隱蔽的房頂上張望凉倚。 院中可真熱鬧,春花似錦嫂沉、人聲如沸稽寒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杏糙。三九已至,卻和暖如春蚓土,著一層夾襖步出監(jiān)牢的瞬間宏侍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工蜀漆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谅河,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓确丢,卻偏偏與公主長得像绷耍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鲜侥,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內容

  • 原文地址:來剃毒,控制一下 Goroutine 的并發(fā)數量 問題 在這里,假設 userCount 是一個外部傳入的參...
    EDDYCJY閱讀 2,988評論 1 10
  • go并發(fā)編程入門到放棄 并發(fā)和并行 并發(fā):一個處理器同時處理多個任務搂赋。 并行:多個處理器或者是多核的處理器同時處理...
    yangyunfeng閱讀 549評論 0 2
  • Channel 是什么赘阀? channel,通道脑奠,本質上是一個通信對象基公,goroutine 之間可以使用它來通信。從...
    癩痢頭閱讀 761評論 0 0
  • Go語言中的并發(fā)編程 并發(fā)是編程里面一個非常重要的概念宋欺,Go語言在語言層面天生支持并發(fā)轰豆,這也是Go語言流行的一個很...
    吳佳浩閱讀 355評論 0 1
  • 16宿命:用概率思維提高你的勝算 以前的我是風險厭惡者,不喜歡去冒險齿诞,但是人生放棄了冒險酸休,也就放棄了無數的可能。 ...
    yichen大刀閱讀 6,033評論 0 4