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  }