github.com/weedge/lib@v0.0.0-20230424045628-a36dcc1d90e4/pool/workerpool/workerpool.go (about)

     1  package workerpool
     2  
     3  import (
     4  	"runtime"
     5  	"runtime/debug"
     6  	"sync"
     7  	"sync/atomic"
     8  	"time"
     9  
    10  	"github.com/weedge/lib/log"
    11  	"github.com/weedge/lib/strings"
    12  )
    13  
    14  // workerChanCap determines whether the channel of a worker should be a buffered channel
    15  // to get the best performance. Inspired by fasthttp at
    16  // https://github.com/valyala/fasthttp/blob/master/workerpool.go#L155
    17  var workerChanCap = func() int {
    18  	// Use blocking channel if GOMAXPROCS=1.
    19  	// This switches context from sender to receiver immediately,
    20  	// which results in higher performance (under go1.5 at least).
    21  	if runtime.GOMAXPROCS(0) == 1 {
    22  		return 0
    23  	}
    24  
    25  	// Use non-blocking workerChan if GOMAXPROCS>1,
    26  	// since otherwise the sender might be dragged down if the receiver is CPU-bound.
    27  	return 1
    28  }()
    29  
    30  type WorkerPool struct {
    31  	minWorkerNum      int32      //worker goroutine 最小数目
    32  	maxWorkerNum      int32      //worker goroutine 最大数目
    33  	curWorkerNum      int32      //当前worker goroutine数量
    34  	stat              int32      //任务工作池状态
    35  	addTaskStat       int32      //添加任务状态
    36  	chAddWorker       chan int32 //添加worker ch -> watchAddWorker
    37  	addWorkerLastTime int64      //最新添加worker时间
    38  	wg                *sync.WaitGroup
    39  	chWorkTask        chan Task
    40  	workers           []*Worker
    41  	indexWorkers      int // watch workers 循环下标
    42  	lock              *sync.Mutex
    43  }
    44  
    45  func NewWorkerPool(minWorkerNum, maxWorkerNum, taskChSize int) *WorkerPool {
    46  	if minWorkerNum <= 0 || minWorkerNum > maxWorkerNum {
    47  		log.Error("worker pool init error , the min number: ", minWorkerNum, " the max number: ", maxWorkerNum)
    48  		return nil
    49  	}
    50  
    51  	if taskChSize <= 0 {
    52  		log.Error("worker pool init error , the nChan : ", taskChSize)
    53  		return nil
    54  	}
    55  
    56  	wp := &WorkerPool{
    57  		minWorkerNum: int32(minWorkerNum),
    58  		maxWorkerNum: int32(maxWorkerNum),
    59  		wg:           &sync.WaitGroup{},
    60  		lock:         &sync.Mutex{},
    61  	}
    62  
    63  	wp.init(taskChSize)
    64  
    65  	return wp
    66  }
    67  
    68  func (wp *WorkerPool) init(taskChSize int) {
    69  	if atomic.LoadInt32(&(wp.stat)) > 0 {
    70  		log.Warn("worker pool is already initialized ! the stat is ", wp.stat)
    71  		return
    72  	}
    73  
    74  	atomic.SwapInt32(&(wp.stat), WorkerPool_Stat_Start)
    75  
    76  	//wp.chWorkTask = make(chan Task, workerChanCap)
    77  	wp.chWorkTask = make(chan Task, taskChSize)
    78  	wp.chAddWorker = make(chan int32, 1)
    79  	wp.workers = make([]*Worker, 0, wp.maxWorkerNum)
    80  	for i := int32(0); i < wp.maxWorkerNum; i++ {
    81  		wp.workers = append(wp.workers, newWorker(wp))
    82  	}
    83  
    84  	log.Info(" init worker pool ok , the min number:  ", wp.minWorkerNum, " the max number : ", wp.maxWorkerNum)
    85  
    86  	return
    87  }
    88  
    89  func (wp *WorkerPool) Run() {
    90  	for i := int32(0); i < wp.minWorkerNum; i++ {
    91  		if nil == wp.workers[i] {
    92  			log.Error(" workers is  nil ,the index is", i)
    93  			return
    94  		}
    95  
    96  		atomic.AddInt32(&(wp.curWorkerNum), 1)
    97  		atomic.SwapInt32(&(wp.workers[i].hasGoroutineRunning), 1)
    98  		wp.wg.Add(1)
    99  		go wp.workers[i].safelyDo()
   100  	}
   101  
   102  	wp.indexWorkers = int(wp.minWorkerNum)
   103  
   104  	wp.wg.Add(1)
   105  	go wp.watchAddWorker()
   106  	atomic.SwapInt32(&(wp.stat), WorkerPool_Stat_Running)
   107  
   108  	log.Info(" run worker pool ok , run the min number:  ", wp.minWorkerNum, " timeout workers and one watch add worker")
   109  }
   110  
   111  func (wp *WorkerPool) watchAddWorker() {
   112  	debug.SetPanicOnFault(true)
   113  	defer wp.wg.Done()
   114  	defer func() {
   115  		if r := recover(); r != nil {
   116  			log.Error("watch add worker goroutine crash, panic", r, strings.BytesToString(debug.Stack()))
   117  		}
   118  	}()
   119  
   120  	for ch := range wp.chAddWorker {
   121  		for nTimes := int32(0); wp.isWorking() && nTimes < wp.maxWorkerNum; {
   122  			nTimes++
   123  			if nil == wp.workers[wp.indexWorkers] {
   124  				log.Warn(" the workers is nil the index is ", wp.indexWorkers)
   125  				continue
   126  			}
   127  
   128  			if atomic.LoadInt32(&wp.workers[wp.indexWorkers].hasGoroutineRunning) <= 0 {
   129  				wp.addWorker()
   130  				log.Info("create a new worker, current worker number is : ", atomic.LoadInt32(&(wp.curWorkerNum)), " the flag ", ch)
   131  				break
   132  			}
   133  		}
   134  	}
   135  }
   136  
   137  func (wp *WorkerPool) addWorker() {
   138  	atomic.SwapInt32(&wp.workers[wp.indexWorkers].hasGoroutineRunning, 1)
   139  	wp.wg.Add(1)
   140  	go wp.workers[wp.indexWorkers].safelyDo()
   141  	atomic.AddInt32(&(wp.curWorkerNum), 1)
   142  	wp.indexWorkers++
   143  	if int32(wp.indexWorkers) >= wp.maxWorkerNum {
   144  		wp.indexWorkers = 0
   145  	}
   146  
   147  	return
   148  }
   149  
   150  func (wp *WorkerPool) AddTask(task *Task) {
   151  	if nil == task {
   152  		log.Warn("AddTask task is nil")
   153  		return
   154  	}
   155  
   156  	if nil == task.Do {
   157  		log.Warn("add task Do func is nil")
   158  		return
   159  	}
   160  	if nil == task.InParam {
   161  		log.Warn("add task Do func inParam is nil")
   162  		return
   163  	}
   164  	if nil == task.OutParam {
   165  		log.Warn("add task Do func outParam is nil")
   166  		return
   167  	}
   168  
   169  	if atomic.LoadInt32(&(wp.stat)) != WorkerPool_Stat_Running {
   170  		log.Warn("this worker pool is not a running stat! the stat is ", wp.stat)
   171  		return
   172  	}
   173  
   174  	atomic.AddInt32(&wp.addTaskStat, 1)
   175  	wp.chWorkTask <- *task
   176  	atomic.AddInt32(&wp.addTaskStat, -1)
   177  
   178  	wp.addWorkerWhenAddTask()
   179  
   180  	return
   181  }
   182  
   183  // add worker when add task cond:
   184  // 1. 1 < work task num < (min worker num)/2
   185  // 2. cur worker num < max worker num
   186  func (wp *WorkerPool) addWorkerWhenAddTask() {
   187  	chWorkTaskNum := len(wp.chWorkTask)
   188  	if chWorkTaskNum > 1 && int32(chWorkTaskNum) > wp.minWorkerNum/2 && atomic.LoadInt32(&wp.curWorkerNum) < wp.maxWorkerNum {
   189  		wp.chAddWorker <- 1
   190  		wp.addWorkerLastTime = time.Now().Unix()
   191  	}
   192  
   193  	return
   194  }
   195  
   196  func (wp *WorkerPool) GetStat() int32 {
   197  	return atomic.LoadInt32(&wp.stat)
   198  }
   199  
   200  func (wp *WorkerPool) GetCurrentGoNumber() int32 {
   201  	return atomic.LoadInt32(&wp.curWorkerNum)
   202  }
   203  
   204  func (wp *WorkerPool) isWorking() bool {
   205  	return atomic.LoadInt32(&(wp.stat)) == WorkerPool_Stat_Running ||
   206  		atomic.LoadInt32(&(wp.stat)) == WorkerPool_Stat_Stoping
   207  }
   208  
   209  func (wp *WorkerPool) close() {
   210  	wp.lock.Lock()
   211  	if wp.isWorking() {
   212  		atomic.SwapInt32(&wp.stat, WorkerPool_Stat_Stop)
   213  		for index, _ := range wp.workers {
   214  			atomic.SwapInt32(&wp.workers[index].hasGoroutineRunning, 1) // 不让自启
   215  			//close(wp.workers[index].chWatchGoroutineOut)
   216  			close(wp.workers[index].chExecuteGoroutineOut)
   217  			//close(wp.workers[index].chTaskDoRes)
   218  		}
   219  		close(wp.chAddWorker)
   220  		//close(wp.chWorkTask)
   221  	}
   222  	wp.lock.Unlock()
   223  }
   224  
   225  func (wp *WorkerPool) Stop() {
   226  	if atomic.LoadInt32(&(wp.stat)) != WorkerPool_Stat_Running {
   227  		log.Warn("this worker pool is not a running stat,can't stop ! the stat is ", wp.stat)
   228  		return
   229  	}
   230  	atomic.SwapInt32(&(wp.stat), WorkerPool_Stat_Stoping)
   231  
   232  	//wait add task product over
   233  	stopTime := time.Now().Unix()
   234  	for atomic.LoadInt32(&wp.addTaskStat) > 0 && time.Now().Unix()-stopTime < addWaitingTimeWhenStopPool {
   235  		time.Sleep(10 * time.Millisecond)
   236  	}
   237  
   238  	log.Info("stop worker pool, stop to close chWorkTask -> close worker pool")
   239  	close(wp.chWorkTask) // stop to close chWorkTask -> close worker pool
   240  
   241  	wp.wg.Wait()
   242  }