go語言協程包PoolGroup

PoolGroup

一個人性化的協程管理包,適用於高併發量,簡單,複雜併發業務場景。

安裝 go get github.com/XeiTongXueFlyMe/poolgroup

使用 import “github.com/XeiTongXueFlyMe/poolgroup”

PoolGroup包,分爲group and pool。

group 解決複雜的併發邏輯

pool 解決高併發量

group + pool 能解決帶有複雜邏輯的高併發

優雅的使用併發

示例(獨立組)

import "github.com/XeiTongXueFlyMe/poolgroup/group"

func main(){
    g := group.NewGroup()
    
    g.Go(func() error { return errors.New("hi, i am Task_1") })
    g.Go(func() error { return errors.New("hi, i am Task_2") })
    g.Go(func() error { return errors.New("hi, i am Task_3") })
    
    //阻塞,直到本組中所有的協程都安全的退出
    g.Wait()
}

group的特性

  • 簡單
  • 輕量級
  • panic安全
  • 協程業務回滾
  • 獨立組 => func( ) error
  • 上下文組 => func(ctx context.Context) error
  • 派生樹(父子關係,兄弟關係)
  • 自由組合和派生。

pool的特性

PoolGroup概念圖

group功能探索

panic安全

協程拋出panic,整個組安全運行,group會將panic寫入其 errs

func fPanic() error {
	panic("The err is unknown")
}
//out: [runtime err The err is unknown]
func main(){
    g := group.NewGroup()
    
    g.Go(fPanic)
    g.Go(func() error { return nil })
    g.Go(func() error { return errors.New("runtime err") })
    
    g.Wait()
    fmt.Println(g.GetErrs())
}

協程業務回滾(上下文組)

  1. 子組觸發回滾,其父不回滾
  2. 父組觸發回滾,子組樹全部產生回滾,其中不帶上下文的獨立組及其派生的子樹不回滾
  3. 在同一個group中,併發協程中某一個協程返回錯誤,或則panic時,所有協程執行業務回滾
  4. 協程業務回滾 入口函數爲 .(func() error)
type metaData struct {
    Name       string
    Ext        string
    createTime int64
}
type db struct {
    file []metaData
}
func (db *db) AddFile(ctx context.Context) error {
    db.file = append(db.file, metaData{
        Name:       "golang實戰",
        Ext:        ".pdf",
        createTime: time.Now().Unix(),
    })
    fmt.Println("成功寫入golang實戰.pdf")
    //模擬一個錯誤,由於不可抗拒原因本協程出現錯誤
    //panic("直接panic,也是可以的")
    return errors.New("某個步驟返回錯誤")
}

//DelAllFile is  rollback func
func (db *db) DelAllFile() error {
    db.file = []metaData{}
    fmt.Printf("回滾被執行:")
    fmt.Println(db.file)
    return nil
}
func (db *db) PrintFileMeta(ctx context.Context) error {
    time.Sleep(100 * time.Millisecond)
    fmt.Printf("PrintFileMeta:")
    fmt.Println(db.file)
    return nil
}
//out:
//成功寫入golang實戰.pdf
//PrintFileMeta:[{golang實戰 .pdf 1566304752}]
//回滾被執行:[]
func main(){
    c := db{}
    g := group.NewGroup()
    g.WithContext(context.TODO())
    //g所有協程未返回err或者panic, c.DelAllFile()不會運行
    g.Go(c.AddFile, c.DelAllFile)
    g.Go(c.PrintFileMeta)
    
    g.Wait()
    
    return nil
}

關閉一個group

會觸發協程業務回滾

    g.Close()

讀取派生樹整個協程數量

    g.GetGoroutineNum()

獲取整個派生樹的錯誤

可實時讀取錯誤,併發安全

g.wait()之後調用,獲取本次執行整個派生樹的錯誤

    g.GetErrs()

group 支持 .(func() error) 和.(func(ctx context.Context) error)協程運行入口,那麼如何安全的向協程中帶入參數呢?

  • 不建議在ctx帶入key&Value傳參

下面實列將 a,b 參數帶入協程

func myPrintf(a, b string) error {
    fmt.Println(a, b)
    return nil
}
//out:
//m immm
//i immm
//h immm
func example_8() error {
    a := []string{"h", "i", "m"}
    b := "immm"
    
    g := group.NewGroup()
    for _, v := range a {
        value := v
        g.Go(func() error {
            return myPrintf(value, b)
        })
    }
    g.Wait()
    
    return nil
}

group上下文組支持Context,用於內部派生樹,可用於SOA分佈式架構,微服務架構等,,傳遞鏈路追蹤消息,超時控制,特殊值傳遞等。

func (t *calc) IncreasedCtx(ctx context.Context) error {
    for {
        time.Sleep(1 * time.Second)
        select {
        case <-ctx.Done():
        	return nil
        default:
        }
        t.m.Lock()
        t.value++
        t.m.Unlock()
    }
    return nil
}
func (t *calc) PrintValueCtx(ctx context.Context) error {
for {
    time.Sleep(1 * time.Second)
    select {
    case <-ctx.Done():
    	return nil
    default:
    }
    t.m.Lock()
    fmt.Println(t.value)
    t.m.Unlock()
    return nil
}
}
func main() {
    c := calc{value: 0}
    
    g := group.NewGroup()
    g.WithContext(context.TODO())
    ////10秒後g及其子組中協程全部退出。獨立組節點及其子樹除外
    //g.WithTimeout(context.TODO(), 10*time.Second)
    g.Go(c.IncreasedCtx)
    g.Go(c.PrintValueCtx)
    
    g.Wait()
}

如何創建派生樹,子group全部退出,父group才退出。group中任何一個協程返回錯誤,或則panic,其他協程,其他group照樣運行

一個簡單的派生樹

g

A

a
b
c

B

C

func main() {
    g := group.NewGroup()
    g.Go(func() error { return nil })
    
    A := g.ForkChild()
    A.Go(func() error { return nil })
    B := g.ForkChild()
    B.Go(func() error { return nil })
    C := g.ForkChild()
    C.Go(func() error { return nil })
    
    a := A.ForkChild()
    a.Go(func() error { return nil })
    b := A.ForkChild()
    b.Go(func() error { return nil })
    c := A.ForkChild()
    c.Go(func() error { return nil })
    
    //直到所有的group退出,才退出
    g.Wait()
}

如何在派生樹中,創建父子關係,像線程一樣,父親down機,其子線程停止運行。

下面代碼:
group中任何一個協程返回錯誤,或則panic,本group所有協程退出,其子樹全部退出

//模擬一個協程運行時發生錯誤
func (t *calc) TimeOutErr(ctx context.Context) error {
    time.Sleep(100 * time.Millisecond)
    return errors.New("TimeOut")
}

//out : 所有協程全部退出
func main() {
    c := calc{value: 0}
    
    g := group.NewGroup()
    g.WithContext(context.TODO())
    g.Go(c.IncreasedCtx)
    g.Go(c.TimeOutErr)
    
    A := g.ForkChild()
    A.Go(c.IncreasedCtx)
    B := g.ForkChild()
    B.Go(c.IncreasedCtx)
    
    a := A.ForkChild()
    a.Go(c.IncreasedCtx)
    b := A.ForkChild()
    b.Go(c.IncreasedCtx)
    b.Go(c.IncreasedCtx)
    
    //直到所有的group退出,才退出
    g.Wait()
    fmt.Println("所有協程全部退出")
    return nil
}

如何在派生樹中,創建父子關係後,希望父down機,某個子及其子樹不受影響。

如下示例,g組中某個協程返回錯誤,或則panic, B組及其子樹全部退出,但是A組及其子樹(a,b)不退出(除非自己安全退出)

func main() {
    c := calc{value: 0}
    
    g := group.NewGroup()
    g.WithContext(context.TODO())
    //...
    
    A := g.ForkChild()
    A.DiscardedContext()
    //...
    B := g.ForkChild()
    //...
    
    a := A.ForkChild()
    //...
    b := A.ForkChild()
    //...
    
    //直到所有的group退出,才退出
    g.Wait()
    return nil
}

自由組合和派生,需要注意什麼

  • group 分爲兩種: 獨立組(.(func() error) )上下文組(.(func(ctx context.Context) error))
  • 先調用g.WithContext()等組配置屬性接口,在調用g.Go()。否則會panic
  • 配置接口 g.WithContext() g.WithTimeout() g.DiscardedContext()
  • g.Go() 正確調用方式, err : = g.Go(f) , 如果f入口函數格式錯誤,g.Go()會返回錯誤。如果你肯定f格式是正確的可以不用接收處理err
  • 子組會繼承父組的屬性(獨立組 or 上下文組),配置接口可以改變這個屬性
func main() {
    c := calc{value: 0}
    
    g := group.NewGroup()
    g.WithContext(context.TODO())
    //...
    
    A := g.ForkChild()
    A.DiscardedContext()
    //...
    B := g.ForkChild()
    //...
    
    aa := A.ForkChild()
    //...
    ab := A.ForkChild()
    ab.WithContext(context.TODO())
    //...
    bb : = B.ForkChild()
    bb.DiscardedContext()
    
    cc := bb.ForkChild()
    cc.WithTimeout(context.TODO(), 100*time.Millisecond)
    
    //直到所有的group退出,才退出
    g.Wait()
    return nil
}

回滾+自由組合和派生,讓你複雜的業務變得簡單

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