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 }