195 lines
4.6 KiB
Go
195 lines
4.6 KiB
Go
package workerpools
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
"go_dreamfactory/lego/utils/container"
|
|
)
|
|
|
|
func newSys(options Options) (sys *WorkerPool, err error) {
|
|
sys = &WorkerPool{
|
|
taskQueue: make(chan *Task, 1),
|
|
maxWorkers: options.MaxWorkers,
|
|
readyWorkers: make(chan chan *Task, options.DefWrokers),
|
|
timeout: options.IdleTimeoutSec,
|
|
tasktimeout: options.Tasktimeout,
|
|
stoppedChan: make(chan struct{}),
|
|
}
|
|
go sys.dispatch()
|
|
return
|
|
}
|
|
|
|
type Task struct {
|
|
f func(ctx context.Context, cancel context.CancelFunc, agrs ...interface{})
|
|
agrs []interface{}
|
|
}
|
|
|
|
type WorkerPool struct {
|
|
maxWorkers int
|
|
timeout time.Duration //超时释放空闲工作人员
|
|
tasktimeout time.Duration //任务执行操超时间
|
|
taskQueue chan *Task
|
|
readyWorkers chan chan *Task
|
|
stoppedChan chan struct{}
|
|
waitingQueue container.Deque
|
|
stopMutex sync.Mutex
|
|
stopped bool
|
|
}
|
|
|
|
func (p *WorkerPool) Stop() {
|
|
p.stop(false)
|
|
}
|
|
func (p *WorkerPool) StopWait() {
|
|
p.stop(true)
|
|
}
|
|
func (p *WorkerPool) IsStop() bool {
|
|
p.stopMutex.Lock()
|
|
defer p.stopMutex.Unlock()
|
|
return p.stopped
|
|
}
|
|
func (p *WorkerPool) Submit(task func(ctx context.Context, cancel context.CancelFunc, agrs ...interface{}), agrs ...interface{}) {
|
|
if task != nil {
|
|
p.taskQueue <- &Task{f: task, agrs: agrs}
|
|
}
|
|
}
|
|
func (p *WorkerPool) SubmitWait(task func(ctx context.Context, cancel context.CancelFunc, agrs ...interface{}), agrs ...interface{}) {
|
|
if task == nil {
|
|
return
|
|
}
|
|
doneChan := make(chan struct{})
|
|
p.taskQueue <- &Task{f: func(ctx context.Context, cancel context.CancelFunc, agrs ...interface{}) {
|
|
task(ctx, cancel, agrs...)
|
|
close(doneChan)
|
|
}, agrs: agrs}
|
|
<-doneChan
|
|
}
|
|
func (p *WorkerPool) WaitingQueueSize() int {
|
|
return p.waitingQueue.Len()
|
|
}
|
|
func (p *WorkerPool) dispatch() {
|
|
defer close(p.stoppedChan)
|
|
timeout := time.NewTimer(p.timeout)
|
|
var (
|
|
workerCount int
|
|
task *Task
|
|
ok, wait bool
|
|
workerTaskChan chan *Task
|
|
)
|
|
startReady := make(chan chan *Task)
|
|
Loop:
|
|
for {
|
|
if p.waitingQueue.Len() != 0 {
|
|
select {
|
|
case task, ok = <-p.taskQueue:
|
|
if !ok {
|
|
break Loop
|
|
}
|
|
if task == nil {
|
|
wait = true
|
|
break Loop
|
|
}
|
|
p.waitingQueue.PushBack(task)
|
|
case workerTaskChan = <-p.readyWorkers:
|
|
// A worker is ready, so give task to worker.
|
|
workerTaskChan <- p.waitingQueue.PopFront().(*Task)
|
|
}
|
|
continue
|
|
}
|
|
timeout.Reset(p.timeout)
|
|
select {
|
|
case task, ok = <-p.taskQueue:
|
|
if !ok || task == nil {
|
|
break Loop
|
|
}
|
|
// Got a task to do.
|
|
select {
|
|
case workerTaskChan = <-p.readyWorkers:
|
|
// A worker is ready, so give task to worker.
|
|
workerTaskChan <- task
|
|
default:
|
|
// No workers ready.
|
|
// Create a new worker, if not at max.
|
|
if workerCount < p.maxWorkers {
|
|
workerCount++
|
|
go func(t *Task) {
|
|
startWorker(startReady, p.readyWorkers, p.tasktimeout)
|
|
// Submit the task when the new worker.
|
|
taskChan := <-startReady
|
|
taskChan <- t
|
|
}(task)
|
|
} else {
|
|
// Enqueue task to be executed by next available worker.
|
|
p.waitingQueue.PushBack(task)
|
|
}
|
|
}
|
|
case <-timeout.C:
|
|
// Timed out waiting for work to arrive. Kill a ready worker.
|
|
if workerCount > 0 {
|
|
select {
|
|
case workerTaskChan = <-p.readyWorkers:
|
|
// A worker is ready, so kill.
|
|
close(workerTaskChan)
|
|
workerCount--
|
|
default:
|
|
// No work, but no ready workers. All workers are busy.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If instructed to wait for all queued tasks, then remove from queue and
|
|
// give to workers until queue is empty.
|
|
if wait {
|
|
for p.waitingQueue.Len() != 0 {
|
|
workerTaskChan = <-p.readyWorkers
|
|
// A worker is ready, so give task to worker.
|
|
workerTaskChan <- p.waitingQueue.PopFront().(*Task)
|
|
}
|
|
}
|
|
|
|
// Stop all remaining workers as they become ready.
|
|
for workerCount > 0 {
|
|
workerTaskChan = <-p.readyWorkers
|
|
close(workerTaskChan)
|
|
workerCount--
|
|
}
|
|
}
|
|
func startWorker(startReady, readyWorkers chan chan *Task, taskouttime time.Duration) {
|
|
go func() {
|
|
taskChan := make(chan *Task)
|
|
var task *Task
|
|
var ok bool
|
|
// Register availability on starReady channel.
|
|
startReady <- taskChan
|
|
for {
|
|
// Read task from dispatcher.
|
|
task, ok = <-taskChan
|
|
if !ok {
|
|
break
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), taskouttime)
|
|
go task.f(ctx, cancel, task.agrs...)
|
|
select {
|
|
case <-ctx.Done():
|
|
cancel()
|
|
}
|
|
readyWorkers <- taskChan
|
|
}
|
|
}()
|
|
}
|
|
func (p *WorkerPool) stop(wait bool) {
|
|
p.stopMutex.Lock()
|
|
defer p.stopMutex.Unlock()
|
|
if p.stopped {
|
|
return
|
|
}
|
|
p.stopped = true
|
|
if wait {
|
|
p.taskQueue <- nil
|
|
}
|
|
close(p.taskQueue)
|
|
<-p.stoppedChan
|
|
}
|