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 }