github.com/dubbogo/gost@v1.14.0/sync/task_pool.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package gxsync 19 20 import ( 21 "fmt" 22 "log" 23 "math/rand" 24 "os" 25 "runtime" 26 "runtime/debug" 27 "sync" 28 "sync/atomic" 29 "time" 30 ) 31 32 import ( 33 gxruntime "github.com/dubbogo/gost/runtime" 34 ) 35 36 type task func() 37 38 // GenericTaskPool represents an generic task pool. 39 type GenericTaskPool interface { 40 // AddTask wait idle worker add task 41 AddTask(t task) bool 42 // AddTaskAlways add task to queues or do it immediately 43 AddTaskAlways(t task) 44 // AddTaskBalance add task to idle queue 45 AddTaskBalance(t task) 46 // Close use to close the task pool 47 Close() 48 // IsClosed use to check pool status. 49 IsClosed() bool 50 } 51 52 func goSafely(fn func()) { 53 gxruntime.GoSafely(nil, false, fn, nil) 54 } 55 56 ///////////////////////////////////////// 57 // Task Pool 58 ///////////////////////////////////////// 59 // task pool: manage task ts 60 type TaskPool struct { 61 TaskPoolOptions 62 63 idx uint32 // round robin index 64 qArray []chan task 65 wg sync.WaitGroup 66 67 once sync.Once 68 done chan struct{} 69 } 70 71 // NewTaskPool build a task pool 72 func NewTaskPool(opts ...TaskPoolOption) GenericTaskPool { 73 var tOpts TaskPoolOptions 74 for _, opt := range opts { 75 opt(&tOpts) 76 } 77 78 tOpts.validate() 79 80 p := &TaskPool{ 81 TaskPoolOptions: tOpts, 82 qArray: make([]chan task, tOpts.tQNumber), 83 done: make(chan struct{}), 84 } 85 86 for i := 0; i < p.tQNumber; i++ { 87 p.qArray[i] = make(chan task, p.tQLen) 88 } 89 p.start() 90 91 return p 92 } 93 94 // start task pool 95 func (p *TaskPool) start() { 96 for i := 0; i < p.tQPoolSize; i++ { 97 p.wg.Add(1) 98 workerID := i 99 q := p.qArray[workerID%p.tQNumber] 100 p.safeRun(workerID, q) 101 } 102 } 103 104 func (p *TaskPool) safeRun(workerID int, q chan task) { 105 gxruntime.GoSafely(nil, false, 106 func() { 107 err := p.run(int(workerID), q) 108 if err != nil { 109 // log error to stderr 110 log.Printf("gost/TaskPool.run error: %s", err.Error()) 111 } 112 }, 113 nil, 114 ) 115 } 116 117 // worker 118 func (p *TaskPool) run(id int, q chan task) error { 119 defer p.wg.Done() 120 121 var ( 122 ok bool 123 t task 124 ) 125 126 for { 127 select { 128 case <-p.done: 129 if 0 < len(q) { 130 return fmt.Errorf("task worker %d exit now while its task buffer length %d is greater than 0", 131 id, len(q)) 132 } 133 134 return nil 135 136 case t, ok = <-q: 137 if ok { 138 func() { 139 defer func() { 140 if r := recover(); r != nil { 141 fmt.Fprintf(os.Stderr, "%s goroutine panic: %v\n%s\n", 142 time.Now(), r, string(debug.Stack())) 143 } 144 }() 145 t() 146 }() 147 } 148 } 149 } 150 } 151 152 // return false when the pool is stop 153 func (p *TaskPool) AddTask(t task) (ok bool) { 154 idx := atomic.AddUint32(&p.idx, 1) 155 id := idx % uint32(p.tQNumber) 156 157 select { 158 case <-p.done: 159 return false 160 default: 161 p.qArray[id] <- t 162 return true 163 } 164 } 165 166 func (p *TaskPool) AddTaskAlways(t task) { 167 id := atomic.AddUint32(&p.idx, 1) % uint32(p.tQNumber) 168 169 select { 170 case p.qArray[id] <- t: 171 return 172 default: 173 goSafely(t) 174 } 175 } 176 177 // do it immediately when no idle queue 178 func (p *TaskPool) AddTaskBalance(t task) { 179 length := len(p.qArray) 180 181 // try len/2 times to lookup idle queue 182 for i := 0; i < length/2; i++ { 183 select { 184 case p.qArray[rand.Intn(length)] <- t: 185 return 186 default: 187 continue 188 } 189 } 190 191 goSafely(t) 192 } 193 194 // stop all tasks 195 func (p *TaskPool) stop() { 196 select { 197 case <-p.done: 198 return 199 default: 200 p.once.Do(func() { 201 close(p.done) 202 }) 203 } 204 } 205 206 // check whether the session has been closed. 207 func (p *TaskPool) IsClosed() bool { 208 select { 209 case <-p.done: 210 return true 211 212 default: 213 return false 214 } 215 } 216 217 func (p *TaskPool) Close() { 218 p.stop() 219 p.wg.Wait() 220 for i := range p.qArray { 221 close(p.qArray[i]) 222 } 223 } 224 225 ///////////////////////////////////////// 226 // Task Pool Simple 227 ///////////////////////////////////////// 228 type taskPoolSimple struct { 229 work chan task // task channel 230 sem chan struct{} // gr pool size 231 232 wg sync.WaitGroup 233 234 once sync.Once 235 done chan struct{} 236 } 237 238 // NewTaskPoolSimple build a simple task pool 239 func NewTaskPoolSimple(size int) GenericTaskPool { 240 if size < 1 { 241 size = runtime.GOMAXPROCS(-1) * 100 242 } 243 return &taskPoolSimple{ 244 work: make(chan task), 245 sem: make(chan struct{}, size), 246 done: make(chan struct{}), 247 } 248 } 249 250 func (p *taskPoolSimple) AddTask(t task) bool { 251 select { 252 case <-p.done: 253 return false 254 default: 255 } 256 257 select { 258 case <-p.done: 259 return false 260 case p.work <- t: 261 case p.sem <- struct{}{}: 262 p.wg.Add(1) 263 go p.worker(t) 264 } 265 return true 266 } 267 268 func (p *taskPoolSimple) AddTaskAlways(t task) { 269 select { 270 case <-p.done: 271 return 272 default: 273 } 274 275 select { 276 case p.work <- t: 277 // exec @t in gr pool 278 return 279 default: 280 } 281 select { 282 case p.work <- t: 283 // exec @t in gr pool 284 case p.sem <- struct{}{}: 285 // add a gr to the gr pool 286 p.wg.Add(1) 287 go p.worker(t) 288 default: 289 // gen a gr temporarily 290 goSafely(t) 291 } 292 } 293 294 func (p *taskPoolSimple) worker(t task) { 295 defer func() { 296 if r := recover(); r != nil { 297 fmt.Fprintf(os.Stderr, "%s goroutine panic: %v\n%s\n", 298 time.Now(), r, string(debug.Stack())) 299 } 300 p.wg.Done() 301 <-p.sem 302 }() 303 t() 304 for t := range p.work { 305 t() 306 } 307 } 308 309 // stop all tasks 310 func (p *taskPoolSimple) stop() { 311 select { 312 case <-p.done: 313 return 314 default: 315 p.once.Do(func() { 316 close(p.done) 317 close(p.work) 318 }) 319 } 320 } 321 322 func (p *taskPoolSimple) Close() { 323 p.stop() 324 // wait until all tasks done 325 p.wg.Wait() 326 } 327 328 // check whether the session has been closed. 329 func (p *taskPoolSimple) IsClosed() bool { 330 select { 331 case <-p.done: 332 return true 333 default: 334 return false 335 } 336 } 337 338 func (p *taskPoolSimple) AddTaskBalance(t task) { p.AddTaskAlways(t) }