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 }