github.com/gogf/gf@v1.16.9/os/grpool/grpool.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/gogf/gf.
     6  
     7  // Package grpool implements a goroutine reusable pool.
     8  package grpool
     9  
    10  import (
    11  	"github.com/gogf/gf/errors/gcode"
    12  	"github.com/gogf/gf/errors/gerror"
    13  
    14  	"github.com/gogf/gf/container/glist"
    15  	"github.com/gogf/gf/container/gtype"
    16  )
    17  
    18  // Goroutine Pool
    19  type Pool struct {
    20  	limit  int         // Max goroutine count limit.
    21  	count  *gtype.Int  // Current running goroutine count.
    22  	list   *glist.List // Job list for asynchronous job adding purpose.
    23  	closed *gtype.Bool // Is pool closed or not.
    24  }
    25  
    26  // Default goroutine pool.
    27  var pool = New()
    28  
    29  // New creates and returns a new goroutine pool object.
    30  // The parameter <limit> is used to limit the max goroutine count,
    31  // which is not limited in default.
    32  func New(limit ...int) *Pool {
    33  	p := &Pool{
    34  		limit:  -1,
    35  		count:  gtype.NewInt(),
    36  		list:   glist.New(true),
    37  		closed: gtype.NewBool(),
    38  	}
    39  	if len(limit) > 0 && limit[0] > 0 {
    40  		p.limit = limit[0]
    41  	}
    42  	return p
    43  }
    44  
    45  // Add pushes a new job to the pool using default goroutine pool.
    46  // The job will be executed asynchronously.
    47  func Add(f func()) error {
    48  	return pool.Add(f)
    49  }
    50  
    51  // AddWithRecover pushes a new job to the pool with specified recover function.
    52  // The optional <recoverFunc> is called when any panic during executing of <userFunc>.
    53  // If <recoverFunc> is not passed or given nil, it ignores the panic from <userFunc>.
    54  // The job will be executed asynchronously.
    55  func AddWithRecover(userFunc func(), recoverFunc ...func(err error)) error {
    56  	return pool.AddWithRecover(userFunc, recoverFunc...)
    57  }
    58  
    59  // Size returns current goroutine count of default goroutine pool.
    60  func Size() int {
    61  	return pool.Size()
    62  }
    63  
    64  // Jobs returns current job count of default goroutine pool.
    65  func Jobs() int {
    66  	return pool.Jobs()
    67  }
    68  
    69  // Add pushes a new job to the pool.
    70  // The job will be executed asynchronously.
    71  func (p *Pool) Add(f func()) error {
    72  	for p.closed.Val() {
    73  		return gerror.NewCode(gcode.CodeInvalidOperation, "pool closed")
    74  	}
    75  	p.list.PushFront(f)
    76  	// Check whether fork new goroutine or not.
    77  	var n int
    78  	for {
    79  		n = p.count.Val()
    80  		if p.limit != -1 && n >= p.limit {
    81  			// No need fork new goroutine.
    82  			return nil
    83  		}
    84  		if p.count.Cas(n, n+1) {
    85  			// Use CAS to guarantee atomicity.
    86  			break
    87  		}
    88  	}
    89  	p.fork()
    90  	return nil
    91  }
    92  
    93  // AddWithRecover pushes a new job to the pool with specified recover function.
    94  // The optional <recoverFunc> is called when any panic during executing of <userFunc>.
    95  // If <recoverFunc> is not passed or given nil, it ignores the panic from <userFunc>.
    96  // The job will be executed asynchronously.
    97  func (p *Pool) AddWithRecover(userFunc func(), recoverFunc ...func(err error)) error {
    98  	return p.Add(func() {
    99  		defer func() {
   100  			if exception := recover(); exception != nil {
   101  				if len(recoverFunc) > 0 && recoverFunc[0] != nil {
   102  					if err, ok := exception.(error); ok {
   103  						recoverFunc[0](err)
   104  					} else {
   105  						recoverFunc[0](gerror.NewCodef(gcode.CodeInternalError, `%v`, exception))
   106  					}
   107  				}
   108  			}
   109  		}()
   110  		userFunc()
   111  	})
   112  }
   113  
   114  // Cap returns the capacity of the pool.
   115  // This capacity is defined when pool is created.
   116  // It returns -1 if there's no limit.
   117  func (p *Pool) Cap() int {
   118  	return p.limit
   119  }
   120  
   121  // Size returns current goroutine count of the pool.
   122  func (p *Pool) Size() int {
   123  	return p.count.Val()
   124  }
   125  
   126  // Jobs returns current job count of the pool.
   127  // Note that, it does not return worker/goroutine count but the job/task count.
   128  func (p *Pool) Jobs() int {
   129  	return p.list.Size()
   130  }
   131  
   132  // fork creates a new goroutine worker.
   133  // Note that the worker dies if the job function panics.
   134  func (p *Pool) fork() {
   135  	go func() {
   136  		defer p.count.Add(-1)
   137  
   138  		var job interface{}
   139  		for !p.closed.Val() {
   140  			if job = p.list.PopBack(); job != nil {
   141  				job.(func())()
   142  			} else {
   143  				return
   144  			}
   145  		}
   146  	}()
   147  }
   148  
   149  // IsClosed returns if pool is closed.
   150  func (p *Pool) IsClosed() bool {
   151  	return p.closed.Val()
   152  }
   153  
   154  // Close closes the goroutine pool, which makes all goroutines exit.
   155  func (p *Pool) Close() {
   156  	p.closed.Set(true)
   157  }