github.com/fufuok/utils@v1.0.10/xsync/mpmcqueue.go (about)

     1  package xsync
     2  
     3  import (
     4  	"runtime"
     5  	"sync/atomic"
     6  	"unsafe"
     7  )
     8  
     9  // A MPMCQueue is a bounded multi-producer multi-consumer concurrent
    10  // queue.
    11  //
    12  // MPMCQueue instances must be created with NewMPMCQueue function.
    13  // A MPMCQueue must not be copied after first use.
    14  //
    15  // Based on the data structure from the following C++ library:
    16  // https://github.com/rigtorp/MPMCQueue
    17  type MPMCQueue struct {
    18  	cap  uint64
    19  	head uint64
    20  	//lint:ignore U1000 prevents false sharing
    21  	hpad [cacheLineSize - 8]byte
    22  	tail uint64
    23  	//lint:ignore U1000 prevents false sharing
    24  	tpad  [cacheLineSize - 8]byte
    25  	slots []slotPadded
    26  }
    27  
    28  type slotPadded struct {
    29  	slot
    30  	//lint:ignore U1000 prevents false sharing
    31  	pad [cacheLineSize - unsafe.Sizeof(slot{})]byte
    32  }
    33  
    34  type slot struct {
    35  	turn uint64
    36  	item interface{}
    37  }
    38  
    39  // NewMPMCQueue creates a new MPMCQueue instance with the given
    40  // capacity.
    41  func NewMPMCQueue(capacity int) *MPMCQueue {
    42  	if capacity < 1 {
    43  		panic("capacity must be positive number")
    44  	}
    45  	return &MPMCQueue{
    46  		cap:   uint64(capacity),
    47  		slots: make([]slotPadded, capacity),
    48  	}
    49  }
    50  
    51  // Enqueue inserts the given item into the queue.
    52  // Blocks, if the queue is full.
    53  func (q *MPMCQueue) Enqueue(item interface{}) {
    54  	head := atomic.AddUint64(&q.head, 1) - 1
    55  	slot := &q.slots[q.idx(head)]
    56  	turn := q.turn(head) * 2
    57  	for atomic.LoadUint64(&slot.turn) != turn {
    58  		runtime.Gosched()
    59  	}
    60  	slot.item = item
    61  	atomic.StoreUint64(&slot.turn, turn+1)
    62  }
    63  
    64  // Dequeue retrieves and removes the item from the head of the queue.
    65  // Blocks, if the queue is empty.
    66  func (q *MPMCQueue) Dequeue() interface{} {
    67  	tail := atomic.AddUint64(&q.tail, 1) - 1
    68  	slot := &q.slots[q.idx(tail)]
    69  	turn := q.turn(tail)*2 + 1
    70  	for atomic.LoadUint64(&slot.turn) != turn {
    71  		runtime.Gosched()
    72  	}
    73  	item := slot.item
    74  	slot.item = nil
    75  	atomic.StoreUint64(&slot.turn, turn+1)
    76  	return item
    77  }
    78  
    79  // TryEnqueue inserts the given item into the queue. Does not block
    80  // and returns immediately. The result indicates that the queue isn't
    81  // full and the item was inserted.
    82  func (q *MPMCQueue) TryEnqueue(item interface{}) bool {
    83  	head := atomic.LoadUint64(&q.head)
    84  	for {
    85  		slot := &q.slots[q.idx(head)]
    86  		turn := q.turn(head) * 2
    87  		if atomic.LoadUint64(&slot.turn) == turn {
    88  			if atomic.CompareAndSwapUint64(&q.head, head, head+1) {
    89  				slot.item = item
    90  				atomic.StoreUint64(&slot.turn, turn+1)
    91  				return true
    92  			}
    93  		} else {
    94  			prevHead := head
    95  			head = atomic.LoadUint64(&q.head)
    96  			if head == prevHead {
    97  				return false
    98  			}
    99  		}
   100  		runtime.Gosched()
   101  	}
   102  }
   103  
   104  // TryDequeue retrieves and removes the item from the head of the
   105  // queue. Does not block and returns immediately. The ok result
   106  // indicates that the queue isn't empty and an item was retrieved.
   107  func (q *MPMCQueue) TryDequeue() (item interface{}, ok bool) {
   108  	tail := atomic.LoadUint64(&q.tail)
   109  	for {
   110  		slot := &q.slots[q.idx(tail)]
   111  		turn := q.turn(tail)*2 + 1
   112  		if atomic.LoadUint64(&slot.turn) == turn {
   113  			if atomic.CompareAndSwapUint64(&q.tail, tail, tail+1) {
   114  				item = slot.item
   115  				ok = true
   116  				slot.item = nil
   117  				atomic.StoreUint64(&slot.turn, turn+1)
   118  				return
   119  			}
   120  		} else {
   121  			prevTail := tail
   122  			tail = atomic.LoadUint64(&q.tail)
   123  			if tail == prevTail {
   124  				return
   125  			}
   126  		}
   127  		runtime.Gosched()
   128  	}
   129  }
   130  
   131  func (q *MPMCQueue) idx(i uint64) uint64 {
   132  	return i % q.cap
   133  }
   134  
   135  func (q *MPMCQueue) turn(i uint64) uint64 {
   136  	return i / q.cap
   137  }