github.com/alphadose/zenq/v2@v2.8.4/select_list.go (about)

     1  package zenq
     2  
     3  import (
     4  	"sync"
     5  	"sync/atomic"
     6  	"unsafe"
     7  )
     8  
     9  // global memory pool for storing and leasing node objects
    10  var (
    11  	nodePool = sync.Pool{New: func() any { return new(node) }}
    12  	nodeGet  = nodePool.Get
    13  	nodePut  = nodePool.Put
    14  )
    15  
    16  // List is a lock-free linked list
    17  // theory -> https://www.cs.rochester.edu/u/scott/papers/1996_PODC_queues.pdf
    18  // pseudocode -> https://www.cs.rochester.edu/research/synchronization/pseudocode/queues.html
    19  type List struct {
    20  	head atomic.Pointer[node]
    21  	tail atomic.Pointer[node]
    22  }
    23  
    24  // NewList returns a new list
    25  func NewList() List {
    26  	n := nodeGet().(*node)
    27  	n.threadPtr, n.dataOut = nil, nil
    28  	n.next.Store(nil)
    29  	var ptr atomic.Pointer[node]
    30  	ptr.Store(n)
    31  	return List{head: ptr, tail: ptr}
    32  }
    33  
    34  // a single node in the linked list
    35  type node struct {
    36  	next      atomic.Pointer[node]
    37  	threadPtr *unsafe.Pointer
    38  	dataOut   *any
    39  }
    40  
    41  // Enqueue inserts a value into the list
    42  func (l *List) Enqueue(threadPtr *unsafe.Pointer, dataOut *any) {
    43  	var (
    44  		n          = nodeGet().(*node)
    45  		tail, next *node
    46  	)
    47  	n.threadPtr, n.dataOut = threadPtr, dataOut
    48  	for {
    49  		tail = l.tail.Load()
    50  		next = tail.next.Load()
    51  		if tail == l.tail.Load() { // are tail and next consistent?
    52  			if next == nil {
    53  				if tail.next.CompareAndSwap(next, n) {
    54  					l.tail.CompareAndSwap(tail, n) // Enqueue is done.  try to swing tail to the inserted node
    55  					return
    56  				}
    57  			} else { // tail was not pointing to the last node
    58  				// try to swing Tail to the next node
    59  				l.tail.CompareAndSwap(tail, next)
    60  			}
    61  		}
    62  	}
    63  }
    64  
    65  // Dequeue removes and returns the value at the head of the queue to the memory pool
    66  // It returns nil if the list is empty
    67  func (l *List) Dequeue() (threadPtr *unsafe.Pointer, dataOut *any) {
    68  	var head, tail, next *node
    69  	for {
    70  		head = l.head.Load()
    71  		tail = l.tail.Load()
    72  		next = head.next.Load()
    73  		if head == l.head.Load() { // are head, tail, and next consistent?
    74  			if head == tail { // is list empty or tail falling behind?
    75  				if next == nil { // is list empty?
    76  					return nil, nil
    77  				}
    78  				// tail is falling behind.  try to advance it
    79  				l.tail.CompareAndSwap(tail, next)
    80  			} else {
    81  				// read value before CAS_node otherwise another dequeue might free the next node
    82  				threadPtr, dataOut = next.threadPtr, next.dataOut
    83  				if l.head.CompareAndSwap(head, next) {
    84  					// sysFreeOS(unsafe.Pointer(head), nodeSize)
    85  					head.threadPtr, head.dataOut = nil, nil
    86  					head.next.Store(nil)
    87  					nodePut(head)
    88  					return // Dequeue is done.  return
    89  				}
    90  			}
    91  		}
    92  	}
    93  }