Go語言協程池實現第二彈

之前寫了Go語言協程池的實踐以及動態QPS的實現,本來計劃就是開始做一些測試了,但是發現協程池的實現有些問題也有一些BUG,所以連夜修改了部分功能。

爲了不咋不明真相的讀者造成困擾,趕緊寫篇文章報告一下。

缺陷&BUG

這裏先把測試中遇到的問題和BUG梳理一下:

  1. 活躍協程數計算錯誤
  2. 執行數量和收到計數錯誤
  3. QPS陡增和陡降的時候,無法及時增加壓力和回收協程
  4. 協程回收存在問題不夠優雅,效率太低

BUG分析

活躍協程數

這裏計數錯誤的原因是因爲在原來的實現中多次使用了ReduceWorker()AddWorker()方法,導致沒有將添加和減少的功能收攏到某一個時機統一處理,有兩處重複使用的問題,導致Active計算錯誤。

執行數量和收到計數錯誤

這裏問題出現在這個方法,本意是設計一個執行固定次數任務的方法。通過將一部分任務合併到同一個任務丟給協程池。但是在實現過程中並沒有將原始的任務單獨計數。同時在記錄到所有收到的任務ReceiveTotal中也存在這個問題。

// ExecuteQps
//  @Description: 執行任務固定次數
//  @receiver pool
//  @param t
//  @param qps
//
func (pool *GorotinesPool) ExecuteQps(t func(), qps int) {
	mutiple := qps / pool.SingleTimes
	remainder := qps % pool.SingleTimes
	for i := 0; i < pool.SingleTimes; i++ {
		pool.Execute(func() {
			for i := 0; i < mutiple; i++ {
				t()
			}
		})
	}
	pool.Execute(func() {
		for i := 0; i < remainder; i++ {
			t()
		}
	})
}

新建回收協程不及時

在QPS陡增陡降的場景測試中,存在2個,需要增加協程數的時候增加較慢,因爲每1秒掃描一次的等待channel,增多增加1個,同樣的也減少1個。這個效率非常低,設計太蠢了。

線程回收不優雅

在舊實現中用的是掃描等待channel判斷是否增加還是減少協程。除了效率低以外,還存在無法及時回收和過度回收(正在運行的協程也會收到一個終止信號)。

舊的方法內容如下(現在看簡直不忍直視):


// balance  
//  @Description: 平衡活躍協程數  
//  @receiver pool  
//  
func (pool *GorotinesPool) balance() {  
   if pool.status {  
      if len(pool.tasks) > 0 && pool.active < int32(pool.Max) {  
         pool.AddWorker()  
      }  
      if len(pool.tasks) == 0 && pool.active > int32(pool.Min) {  
         pool.ReduceWorker()  
      }  
   }  
}

解決方法

重寫回收方法

這裏參照了Java線程池的實現,通過設置一個最大空閒時間,通過這個設置來回收協程,這樣即方便又能照顧到協程運行狀態,避免過度回收。這裏改造了一下worker()方法。

實現內容如下:

  
// worker  
//  @Description: 開始執行協程  
//  @receiver pool  
//  
func (pool *GorotinesPool) worker() {  
   defer func() {  
      if p := recover(); p != nil {  
         log.Printf("execute task fail: %v", p)  
      }  
   }()  
Fun:  
   for {  
      select {  
      case t := <-pool.tasks:  
         atomic.AddInt32(&pool.ExecuteTotal, 1)  
         t()  
      case <-time.After(pool.MaxIdle):  
         if pool.Active > int32(pool.Min) {  
            atomic.AddInt32(&pool.Active, -1)  
            break Fun  
         }  
      }  
   }  
}

這裏順道解決了協程回收效率不高的問題。

協程增加效率

這裏做了2項修改:

  1. 增加掃描後單次增加協程數量,增加等待channel數量等量的協程數
  2. 調整func (pool *GorotinesPool) Execute(t func()) error合併任務的算法,採取固定的數量合併方案

增加協程數量:

// balance  
//  @Description: 平衡活躍協程數  
//  @receiver pool  
//  
func (pool *GorotinesPool) balance() {  
   if pool.status {  
      if len(pool.tasks) > 0 && pool.Active < int32(pool.Max) {  
         for i := 0; i < len(pool.tasks); i++ {  
            if int(pool.Active) < pool.Max {  
               pool.AddWorker()  
            }  
         }  
      }  
   }  
}

調整合並任務方案:

// ExecuteQps
//  @Description: 執行任務固定次數
//  @receiver pool
//  @param t
//  @param qps
//
func (pool *GorotinesPool) ExecuteQps(t func(), qps int) {
	mutiple := qps / pool.SingleTimes
	remainder := qps % pool.SingleTimes
	for i := 0; i < mutiple; i++ {
		pool.Execute(func() {
			atomic.AddInt32(&pool.ExecuteTotal, -1)
			for i := 0; i < pool.SingleTimes; i++ {
				atomic.AddInt32(&pool.ExecuteTotal, 1)
				t()
			}
		})
	}
	pool.Execute(func() {
		atomic.AddInt32(&pool.ExecuteTotal, -1)
		for i := 0; i < remainder; i++ {
			atomic.AddInt32(&pool.ExecuteTotal, 1)
			t()
		}
	})
}

計數不準確

這個純屬BUG,改掉計數錯誤的地方,修復BUG,代碼已經在上述修復代碼中有所體現了。

完整代碼

package execute  
  
import (  
   "errors"  
   "funtester/ftool"   "log"   "sync/atomic"   "time")  
  
type GorotinesPool struct {  
   Max          int  
   Min          int  
   tasks        chan func()  
   status       bool  
   Active       int32  
   ExecuteTotal int32  
   SingleTimes  int  
   addTimeout   time.Duration  
   MaxIdle      time.Duration  
}  
  
type taskType int  
  
const (  
   normal taskType = 0  
   reduce taskType = 1  
)  
  
// GetPool  
//  @Description: 創建線程池  
//  @param max 最大協程數  
//  @param min 最小協程數  
//  @param maxWaitTask 最大任務等待長度  
//  @param timeout 添加任務超時時間,單位s  
//  @return *GorotinesPool  
//  
func GetPool(max, min, maxWaitTask, timeout, maxIdle int) *GorotinesPool {  
   p := &GorotinesPool{  
      Max:          max,  
      Min:          min,  
      tasks:        make(chan func(), maxWaitTask),  
      status:       true,  
      Active:       0,  
      ExecuteTotal: 0,  
      SingleTimes:  10,  
      addTimeout:   time.Duration(timeout) * time.Second,  
      MaxIdle:      time.Duration(maxIdle) * time.Second,  
   }  
   for i := 0; i < min; i++ {  
      p.AddWorker()  
   }  
   go func() {  
      for {  
         if !p.status {  
            break  
         }  
         ftool.Sleep(1000)  
         p.balance()  
      }  
   }()  
   return p  
}  
  
// worker  
//  @Description: 開始執行協程  
//  @receiver pool  
//  
func (pool *GorotinesPool) worker() {  
   defer func() {  
      if p := recover(); p != nil {  
         log.Printf("execute task fail: %v", p)  
      }  
   }()  
Fun:  
   for {  
      select {  
      case t := <-pool.tasks:  
         atomic.AddInt32(&pool.ExecuteTotal, 1)  
         t()  
      case <-time.After(pool.MaxIdle):  
         if pool.Active > int32(pool.Min) {  
            atomic.AddInt32(&pool.Active, -1)  
            break Fun  
         }  
      }  
   }  
}  
  
// Execute  
//  @Description: 執行任務  
//  @receiver pool  
//  @param t  
//  @return error  
//  
func (pool *GorotinesPool) Execute(t func()) error {  
   if pool.status {  
      select {  
      case pool.tasks <- func() {  
         t()  
      }:  
         return nil  
      case <-time.After(pool.addTimeout):  
         return errors.New("add tasks timeout")  
      }  
   } else {  
      return errors.New("pools is down")  
   }  
}  
  
// Wait  
//  @Description: 結束等待任務完成  
//  @receiver pool  
//  
func (pool *GorotinesPool) Wait() {  
   pool.status = false  
Fun:  
   for {  
      if len(pool.tasks) == 0 || pool.Active == 0 {  
         break Fun  
      }  
      ftool.Sleep(1000)  
   }  
   defer close(pool.tasks)  
   log.Printf("execute: %d", pool.ExecuteTotal)  
}  
  
// AddWorker  
//  @Description: 添加worker,協程數加1  
//  @receiver pool  
//  
func (pool *GorotinesPool) AddWorker() {  
   atomic.AddInt32(&pool.Active, 1)  
   go pool.worker()  
}  
  
// balance  
//  @Description: 平衡活躍協程數  
//  @receiver pool  
//  
func (pool *GorotinesPool) balance() {  
   if pool.status {  
      if len(pool.tasks) > 0 && pool.Active < int32(pool.Max) {  
         for i := 0; i < len(pool.tasks); i++ {  
            if int(pool.Active) < pool.Max {  
               pool.AddWorker()  
            }  
         }  
      }  
   }  
}  
  
// ExecuteQps  
//  @Description: 執行任務固定次數  
//  @receiver pool  
//  @param t  
//  @param qps  
//  
func (pool *GorotinesPool) ExecuteQps(t func(), qps int) {  
   mutiple := qps / pool.SingleTimes  
   remainder := qps % pool.SingleTimes  
   for i := 0; i < mutiple; i++ {  
      pool.Execute(func() {  
         atomic.AddInt32(&pool.ExecuteTotal, -1)  
         for i := 0; i < pool.SingleTimes; i++ {  
            atomic.AddInt32(&pool.ExecuteTotal, 1)  
            t()  
         }  
      })  
   }  
   pool.Execute(func() {  
      atomic.AddInt32(&pool.ExecuteTotal, -1)  
      for i := 0; i < remainder; i++ {  
         atomic.AddInt32(&pool.ExecuteTotal, 1)  
         t()  
      }  
   })  
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章