github.com/sohaha/zlsgo@v1.7.13-0.20240501141223-10dd1a906f76/zpool/pool.go (about)

     1  package zpool
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/sohaha/zlsgo/zdi"
    11  	"github.com/sohaha/zlsgo/zutil"
    12  )
    13  
    14  type (
    15  	// Task Define function callbacks
    16  	Task     interface{}
    17  	taskfn   func() error
    18  	WorkPool struct {
    19  		workers     sync.Pool
    20  		injector    zdi.Injector
    21  		queue       chan *worker
    22  		usedNum     *zutil.Int64
    23  		activeNum   *zutil.Int64
    24  		panicFunc   PanicFunc
    25  		New         func()
    26  		minIdle     uint
    27  		maxIdle     uint
    28  		releaseTime time.Duration
    29  		mu          sync.RWMutex
    30  		closed      bool
    31  	}
    32  	worker struct {
    33  		jobQueue  chan taskfn
    34  		stop      chan struct{}
    35  		Parameter chan []interface{}
    36  	}
    37  	PanicFunc func(err error)
    38  )
    39  
    40  var (
    41  	ErrPoolClosed  = errors.New("pool has been closed")
    42  	ErrWaitTimeout = errors.New("pool wait timeout")
    43  )
    44  
    45  // type Options func(*WorkPool)
    46  // // func WithReleaseTime
    47  // func NewCustom(min int, opt Options) *WorkPool {
    48  // 	w := New(min)
    49  // 	if opt != nil {
    50  // 		opt(w)
    51  // 	}
    52  // 	return w
    53  // }
    54  
    55  func New(size int, max ...int) *WorkPool {
    56  	minIdle := uint(size)
    57  	if minIdle <= 0 {
    58  		minIdle = 1
    59  	}
    60  	maxIdle := minIdle
    61  	if len(max) > 0 && max[0] > 0 {
    62  		m := uint(max[0])
    63  		if m > maxIdle {
    64  			maxIdle = m
    65  		}
    66  	}
    67  
    68  	w := &WorkPool{
    69  		minIdle:     minIdle,
    70  		maxIdle:     maxIdle,
    71  		injector:    zdi.New(),
    72  		queue:       make(chan *worker, maxIdle),
    73  		usedNum:     zutil.NewInt64(0),
    74  		activeNum:   zutil.NewInt64(0),
    75  		releaseTime: time.Second * 60,
    76  		workers: sync.Pool{New: func() interface{} {
    77  			return &worker{
    78  				jobQueue:  make(chan taskfn),
    79  				Parameter: make(chan []interface{}),
    80  				stop:      make(chan struct{}),
    81  			}
    82  		}},
    83  	}
    84  	// todo 定时把队列写入到 chan
    85  
    86  	return w
    87  }
    88  
    89  // Do Add to the workpool and implement
    90  func (wp *WorkPool) Do(fn Task) error {
    91  	return wp.do(context.Background(), wp.handlerFunc(fn), nil)
    92  }
    93  
    94  func (wp *WorkPool) DoWithTimeout(fn Task, t time.Duration) error {
    95  	ctx, canle := context.WithTimeout(context.Background(), t)
    96  	defer canle()
    97  	return wp.do(ctx, wp.handlerFunc(fn), nil)
    98  }
    99  
   100  // PanicFunc Do Add to the workpool and implement
   101  func (wp *WorkPool) PanicFunc(handler PanicFunc) {
   102  	wp.panicFunc = handler
   103  }
   104  
   105  func (wp *WorkPool) do(cxt context.Context, fn taskfn, param []interface{}) error {
   106  	if wp.IsClosed() {
   107  		return ErrPoolClosed
   108  	}
   109  	wp.activeNum.Add(1)
   110  	wp.mu.Lock()
   111  	run := func(w *worker) {
   112  		if fn != nil {
   113  			w.jobQueue <- fn
   114  		}
   115  	}
   116  	add := func() *worker {
   117  		wp.usedNum.Add(1)
   118  		wp.mu.Unlock()
   119  		w := wp.workers.Get().(*worker)
   120  		go w.createGoroutines(wp, wp.queue, wp.panicFunc)
   121  		return w
   122  	}
   123  	select {
   124  	case w := <-wp.queue:
   125  		wp.mu.Unlock()
   126  		if w != nil {
   127  			run(w)
   128  		} else {
   129  			return ErrPoolClosed
   130  		}
   131  	default:
   132  		switch {
   133  		case uint(wp.usedNum.Load()) >= wp.minIdle:
   134  			if uint(wp.usedNum.Load()) < wp.maxIdle {
   135  				w := add()
   136  				run(w)
   137  				return nil
   138  			}
   139  			wp.mu.Unlock()
   140  			select {
   141  			case <-cxt.Done():
   142  				wp.activeNum.Sub(1)
   143  				return ErrWaitTimeout
   144  			case w := <-wp.queue:
   145  				if w != nil {
   146  					run(w)
   147  				} else {
   148  					return ErrPoolClosed
   149  				}
   150  			}
   151  		case uint(wp.usedNum.Load()) < wp.minIdle:
   152  			w := add()
   153  			run(w)
   154  		default:
   155  			wp.mu.Unlock()
   156  		}
   157  	}
   158  	return nil
   159  }
   160  
   161  // IsClosed Has it been closed
   162  func (wp *WorkPool) IsClosed() bool {
   163  	wp.mu.RLock()
   164  	b := wp.closed
   165  	wp.mu.RUnlock()
   166  	return b
   167  }
   168  
   169  // Close  the pool
   170  func (wp *WorkPool) Close() {
   171  	if wp.IsClosed() {
   172  		return
   173  	}
   174  	wp.mu.Lock()
   175  	wp.closed = true
   176  	for 0 < uint(wp.usedNum.Load()) {
   177  		wp.usedNum.Sub(1)
   178  		worker := <-wp.queue
   179  		worker.close()
   180  	}
   181  	wp.mu.Unlock()
   182  }
   183  
   184  // Wait for the task to finish
   185  func (wp *WorkPool) Wait() {
   186  	for 0 < uint(wp.activeNum.Load()) {
   187  		time.Sleep(100 * time.Millisecond)
   188  	}
   189  }
   190  
   191  // Pause pause
   192  func (wp *WorkPool) Pause() {
   193  	wp.AdjustSize(0)
   194  }
   195  
   196  // Continue to work
   197  func (wp *WorkPool) Continue(workerNum ...int) {
   198  	num := int(wp.maxIdle)
   199  	if len(workerNum) > 0 {
   200  		num = workerNum[0]
   201  	}
   202  	wp.AdjustSize(num)
   203  }
   204  
   205  // Cap get the number of coroutines
   206  func (wp *WorkPool) Cap() uint {
   207  	return uint(wp.usedNum.Load())
   208  }
   209  
   210  // AdjustSize adjust the pool size
   211  func (wp *WorkPool) AdjustSize(workSize int) {
   212  	wp.mu.Lock()
   213  	defer wp.mu.Unlock()
   214  	if wp.closed {
   215  		return
   216  	}
   217  
   218  	oldSize := wp.minIdle
   219  	newSize := uint(workSize)
   220  	if newSize > wp.maxIdle {
   221  		newSize = wp.maxIdle
   222  	}
   223  	wp.minIdle = newSize
   224  
   225  	if workSize > 0 && oldSize < wp.minIdle {
   226  		for uint(wp.usedNum.Load()) < wp.minIdle {
   227  			wp.usedNum.Add(1)
   228  			w := wp.workers.Get().(*worker)
   229  			go w.createGoroutines(wp, wp.queue, wp.panicFunc)
   230  			wp.queue <- w
   231  		}
   232  	}
   233  	for wp.minIdle < uint(wp.usedNum.Load()) {
   234  		wp.usedNum.Sub(1)
   235  		worker := <-wp.queue
   236  		worker.stop <- struct{}{}
   237  		wp.workers.Put(worker)
   238  	}
   239  }
   240  
   241  func (wp *WorkPool) PreInit() error {
   242  	if wp.IsClosed() {
   243  		return ErrPoolClosed
   244  	}
   245  	wp.mu.Lock()
   246  	for uint(wp.usedNum.Load()) < wp.minIdle {
   247  		wp.usedNum.Add(1)
   248  		w := wp.workers.Get().(*worker)
   249  		go w.createGoroutines(wp, wp.queue, wp.panicFunc)
   250  		wp.queue <- w
   251  	}
   252  	wp.mu.Unlock()
   253  	return nil
   254  }
   255  
   256  func (w *worker) createGoroutines(wp *WorkPool, q chan<- *worker, handler PanicFunc) {
   257  	defer func() {
   258  		if r := recover(); r != nil {
   259  			wp.activeNum.Sub(1)
   260  			err, ok := r.(error)
   261  			if !ok {
   262  				err = fmt.Errorf("%v", r)
   263  			}
   264  			if err != nil && handler != nil {
   265  				handler(err)
   266  			}
   267  			go w.createGoroutines(wp, q, handler)
   268  			q <- w
   269  		}
   270  	}()
   271  	if wp.releaseTime > 0 {
   272  		timer := time.NewTimer(wp.releaseTime)
   273  		defer timer.Stop()
   274  		for {
   275  			select {
   276  			case job := <-w.jobQueue:
   277  				timer.Stop()
   278  				err := job()
   279  				if err != nil && handler != nil {
   280  					handler(err)
   281  				}
   282  				wp.activeNum.Sub(1)
   283  				q <- w
   284  				timer.Reset(wp.releaseTime)
   285  			// case parameter := <-w.Parameter:
   286  			// 	q <- w
   287  			case <-timer.C:
   288  				<-wp.queue
   289  				wp.usedNum.Sub(1)
   290  				return
   291  			case <-w.stop:
   292  				return
   293  			}
   294  		}
   295  	} else {
   296  		for {
   297  			select {
   298  			case job := <-w.jobQueue:
   299  				err := job()
   300  				if err != nil && handler != nil {
   301  					handler(err)
   302  				}
   303  				wp.activeNum.Sub(1)
   304  				q <- w
   305  			case <-w.stop:
   306  				return
   307  			}
   308  		}
   309  	}
   310  }
   311  
   312  func (w *worker) close() {
   313  	w.stop <- struct{}{}
   314  	close(w.stop)
   315  	close(w.jobQueue)
   316  }