gf架構之grpool – 高效能的goroutine池

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

grpool

Go語言中的goroutine雖然相對於系統線程來說比較輕量級,但是在高並發量下的goroutine頻繁建立和銷毀對於效能損耗以及GC來說壓力也不小。充分將goroutine複用,減少goroutine的建立/銷毀的效能損耗,這便是grpool對goroutine進行池化封裝的目的。例如,針對於100W個執行任務,使用goroutine的話需要不停建立並銷毀100W個goroutine,而使用grpool也許底層只需要幾千個goroutine便能充分複用地執行完成所有任務。經測試,在高並發下grpool的效能比原生的goroutine高出幾倍到數百倍!並且隨之也極大地降低了記憶體使用量率。

效能測試報告:http://johng.cn/grpool-perfor...

方法列表

func Add(f func())func Jobs() intfunc SetExpire(expire int)func SetSize(size int)func Size() inttype Pool    func New(expire int, sizes ...int) *Pool    func (p *Pool) Add(f func())    func (p *Pool) Close()    func (p *Pool) Jobs() int    func (p *Pool) SetExpire(expire int)    func (p *Pool) SetSize(size int)    func (p *Pool) Size() int

通過grpool.New方法建立一個goroutine池,並給定池中goroutine的有效時間,單位為,第二個參數為非必需參數,用於限定池中的工作goroutine數量,預設為不限制。需要注意的是,任務可以不停地往池中添加,沒有限制,但是工作的goroutine是可以做限制的。我們可以通過Size()方法查詢當前的工作goroutine數量,使用Jobs()方法查詢當前池中待處理的任務數量。

同時,池的大小和goroutine有效期間可以通過SetSize和SetExpire方法在運行時進行動態改變。這一點特性使得協程池的管理更加靈活,但是也增加了一定的維護風險,因為你無法得知池的屬性在哪個地方被執行了修改。

同時,為便於使用,grpool包提供了預設的goroutine池,直接通過grpool.Add即可往預設的池中新增工作,任務參數必須是一個 func() 類型的函數/方法。

使用樣本

1、使用預設的goroutine池,限制10個工作goroutine執行1000個任務。

https://gitee.com/johng/gf/bl...

package mainimport (    "time"    "fmt"    "gitee.com/johng/gf/g/os/gtime"    "gitee.com/johng/gf/g/os/grpool")func job() {    time.Sleep(1*time.Second)}func main() {    grpool.SetSize(10)    for i := 0; i < 1000; i++ {        grpool.Add(job)    }    gtime.SetInterval(2*time.Second, func() bool {        fmt.Println("size:", grpool.Size())        fmt.Println("jobs:", grpool.Jobs())        return true    })    select {}}

這段程式中的任務函數的功能是sleep 1秒鐘,這樣便能充分展示出goroutine數量限制功能。其中,我們使用了gtime.SetInterval定時器每隔2秒鐘列印出當前預設池中的工作goroutine數量以及待處理的任務數量。

2、我們再來看一個新手經常容易出錯的例子

https://gitee.com/johng/gf/bl...

package mainimport (    "fmt"    "sync"    "gitee.com/johng/gf/g/os/grpool")func main() {    wg := sync.WaitGroup{}    for i := 0; i < 10; i++ {        wg.Add(1)        grpool.Add(func() {            fmt.Println(i)            wg.Done()        })    }    wg.Wait()}

我們這段代碼的目的是要順序地列印出0-9,然而運行後卻輸出:

10101010101010101010

為什麼呢?這裡的執行結果無論是採用go關鍵字來執行還是grpool來執行都是如此。原因是,對於非同步線程/協程來講,函數進行進行非同步執行註冊時,該函數並未真正開始執行(註冊時只在goroutine的棧中儲存了變數i的記憶體位址),而一旦開始執行時函數才會去讀取變數i的值,而這個時候變數i的值已經自增到了10。
清楚原因之後,改進方案也很簡單了,就是在註冊非同步執行函數的時候,把當時變數i的值也一併傳遞擷取;或者把當前變數i的值賦值給一個不會改變的臨時變數,在函數中使用該臨時變數而不是直接使用變數i。

改進後的範例程式碼如下:

1)、使用go關鍵字

https://gitee.com/johng/gf/bl...

package mainimport (    "fmt"    "sync")func main() {    wg := sync.WaitGroup{}    for i := 0; i < 10; i++ {        wg.Add(1)        go func(v int){            fmt.Println(v)            wg.Done()        }(i)    }    wg.Wait()}

執行後,輸出結果為:

9012345678

注意,非同步執行時並不會保證按照函數註冊時的順序執行,以下同理。

2)、使用臨時變數

https://gitee.com/johng/gf/bl...

package mainimport (    "fmt"    "sync"    "gitee.com/johng/gf/g/os/grpool")func main() {    wg := sync.WaitGroup{}    for i := 0; i < 10; i++ {        wg.Add(1)        v := i        grpool.Add(func() {            fmt.Println(v)            wg.Done()        })    }    wg.Wait()}

執行後,輸出結果為:

9012345678

這裡可以看到,使用grpool進行任務註冊時,只能使用func()類型的參數,因此無法在任務註冊時把變數i的值註冊進去,因此只能採用臨時變數的形式來傳遞當前變數i的值。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.