github.com/alphadose/zenq/v2@v2.8.4/thread_parker.go (about) 1 package zenq 2 3 import ( 4 "sync/atomic" 5 "unsafe" 6 ) 7 8 // ThreadParker is a data-structure used for sleeping and waking up goroutines on user call 9 // useful for saving up resources by parking excess goroutines and pre-empt them when required with minimal latency overhead 10 // Uses the same lock-free linked list implementation as in `list.go` 11 type ThreadParker[T any] struct { 12 head atomic.Pointer[parkSpot[T]] 13 tail atomic.Pointer[parkSpot[T]] 14 } 15 16 // NewThreadParker returns a new thread parker. 17 func NewThreadParker[T any](spot *parkSpot[T]) *ThreadParker[T] { 18 var ptr atomic.Pointer[parkSpot[T]] 19 ptr.Store(spot) 20 return &ThreadParker[T]{head: ptr, tail: ptr} 21 } 22 23 // a single parked goroutine 24 type parkSpot[T any] struct { 25 next atomic.Pointer[parkSpot[T]] 26 threadPtr unsafe.Pointer 27 value T 28 } 29 30 // Park parks the current calling goroutine 31 // This keeps only one parked goroutine in state at all times 32 // the parked goroutine is called with minimal overhead via goready() due to both being in userland 33 // This ensures there is no thundering herd https://en.wikipedia.org/wiki/Thundering_herd_problem 34 func (tp *ThreadParker[T]) Park(nextNode *parkSpot[T]) { 35 var tail, next *parkSpot[T] 36 for { 37 tail = tp.tail.Load() 38 next = tail.next.Load() 39 if tail == tp.tail.Load() { 40 if next == nil { 41 if tail.next.CompareAndSwap(next, nextNode) { 42 tp.tail.CompareAndSwap(tail, nextNode) 43 return 44 } 45 } else { 46 tp.tail.CompareAndSwap(tail, next) 47 } 48 } 49 } 50 } 51 52 // Ready calls one parked goroutine from the queue if available 53 func (tp *ThreadParker[T]) Ready() (data T, ok bool, freeable *parkSpot[T]) { 54 var head, tail, next *parkSpot[T] 55 for { 56 head = tp.head.Load() 57 tail = tp.tail.Load() 58 next = head.next.Load() 59 if head == tp.head.Load() { 60 if head == tail { 61 if next == nil { 62 return 63 } 64 tp.tail.CompareAndSwap(tail, next) 65 } else { 66 safe_ready(next.threadPtr) 67 data, ok = next.value, true 68 if tp.head.CompareAndSwap(head, next) { 69 freeable = head 70 freeable.threadPtr = nil 71 freeable.next.Store(nil) 72 return 73 } 74 } 75 } 76 } 77 }