github.com/alphadose/itogami@v0.4.1-0.20221016160904-c25d0a36bfe7/pool.go (about)

     1  package itogami
     2  
     3  import (
     4  	"sync"
     5  	"sync/atomic"
     6  	"unsafe"
     7  )
     8  
     9  // a single slot for a worker in Pool
    10  type slot struct {
    11  	threadPtr unsafe.Pointer
    12  	task      func()
    13  }
    14  
    15  // Pool represents the thread-pool for performing any kind of task ( type -> func() {} )
    16  type Pool struct {
    17  	currSize uint64
    18  	_p1      [cacheLinePadSize - unsafe.Sizeof(uint64(0))]byte
    19  	maxSize  uint64
    20  	_p2      [cacheLinePadSize - unsafe.Sizeof(uint64(0))]byte
    21  	// using a stack keeps cpu caches warm based on FILO property
    22  	top atomic.Pointer[node]
    23  	_p3 [cacheLinePadSize - unsafe.Sizeof(atomic.Pointer[node]{})]byte
    24  }
    25  
    26  // NewPool returns a new thread pool
    27  func NewPool(size uint64) *Pool {
    28  	return &Pool{maxSize: size}
    29  }
    30  
    31  // Submit submits a new task to the pool
    32  // it first tries to use already parked goroutines from the stack if any
    33  // if there are no available worker goroutines, it tries to add a
    34  // new goroutine to the pool if the pool capacity is not exceeded
    35  // in case the pool capacity hit its maximum limit, this function yields the processor to other
    36  // goroutines and loops again for finding available workers
    37  func (self *Pool) Submit(task func()) {
    38  	var s *slot
    39  	for {
    40  		if s = self.pop(); s != nil {
    41  			s.task = task
    42  			safe_ready(s.threadPtr)
    43  			return
    44  		} else if atomic.AddUint64(&self.currSize, 1) <= self.maxSize {
    45  			s = &slot{task: task}
    46  			go self.loopQ(s)
    47  			return
    48  		} else {
    49  			atomic.AddUint64(&self.currSize, uint64SubtractionConstant)
    50  			mcall(gosched_m)
    51  		}
    52  	}
    53  }
    54  
    55  // loopQ is the looping function for every worker goroutine
    56  func (self *Pool) loopQ(s *slot) {
    57  	// store self goroutine pointer
    58  	s.threadPtr = GetG()
    59  	for {
    60  		// exec task
    61  		s.task()
    62  		// notify availability by pushing self reference into stack
    63  		self.push(s)
    64  		// park and wait for call
    65  		mcall(fast_park)
    66  	}
    67  }
    68  
    69  // global memory pool for all items used in Pool
    70  var (
    71  	itemPool  = sync.Pool{New: func() any { return new(node) }}
    72  	itemAlloc = itemPool.Get
    73  	itemFree  = itemPool.Put
    74  )
    75  
    76  // internal lock-free stack implementation for parking and waking up goroutines
    77  // Credits -> https://github.com/golang-design/lockfree
    78  
    79  // a single node in this stack
    80  type node struct {
    81  	next  atomic.Pointer[node]
    82  	value *slot
    83  }
    84  
    85  // pop pops value from the top of the stack
    86  func (self *Pool) pop() (value *slot) {
    87  	var top, next *node
    88  	for {
    89  		top = self.top.Load()
    90  		if top == nil {
    91  			return
    92  		}
    93  		next = top.next.Load()
    94  		if self.top.CompareAndSwap(top, next) {
    95  			value = top.value
    96  			top.value = nil
    97  			top.next.Store(nil)
    98  			itemFree(top)
    99  			return
   100  		}
   101  	}
   102  }
   103  
   104  // push pushes a value on top of the stack
   105  func (self *Pool) push(v *slot) {
   106  	var (
   107  		top  *node
   108  		item = itemAlloc().(*node)
   109  	)
   110  	item.value = v
   111  	for {
   112  		top = self.top.Load()
   113  		item.next.Store(top)
   114  		if self.top.CompareAndSwap(top, item) {
   115  			return
   116  		}
   117  	}
   118  }