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())
}
協程業務回滾(上下文組)
- 子組觸發回滾,其父不回滾
- 父組觸發回滾,子組樹全部產生回滾,其中不帶上下文的獨立組及其派生的子樹不回滾
- 在同一個group中,併發協程中某一個協程返回錯誤,或則panic時,所有協程執行業務回滾
- 協程業務回滾 入口函數爲 .(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
}