github.com/dubbogo/gost@v1.14.0/container/queue/poolqueue.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 //refs:https://github.com/golang/go/blob/2333c6299f340a5f76a73a4fec6db23ffa388e97/src/sync/poolqueue.go 19 package gxqueue 20 21 import ( 22 "errors" 23 "sync/atomic" 24 "unsafe" 25 ) 26 27 // SPMCLockFreeQ is a lock-free queue. 28 type SPMCLockFreeQ interface { 29 PushHead(val interface{}) bool 30 PopHead() (interface{}, bool) 31 PopTail() (interface{}, bool) 32 } 33 34 // poolDequeue is a lock-free fixed-size single-producer, 35 // multi-consumer queue. The single producer can both push and pop 36 // from the head, and consumers can pop from the tail. 37 // 38 // It has the added feature that it nils out unused slots to avoid 39 // unnecessary retention of objects. This is important for sync.Pool, 40 // but not typically a property considered in the literature. 41 type poolDequeue struct { 42 // headTail packs together a 32-bit head index and a 32-bit 43 // tail index. Both are indexes into vals modulo len(vals)-1. 44 // 45 // tail = index of oldest data in queue 46 // head = index of next slot to fill 47 // 48 // Slots in the range [tail, head) are owned by consumers. 49 // A consumer continues to own a slot outside this range until 50 // it nils the slot, at which point ownership passes to the 51 // producer. 52 // 53 // The head index is stored in the most-significant bits so 54 // that we can atomically add to it and the overflow is 55 // harmless. 56 headTail uint64 57 58 // vals is a ring buffer of interface{} values stored in this 59 // dequeue. The size of this must be a power of 2. 60 // 61 // vals[i].typ is nil if the slot is empty and non-nil 62 // otherwise. A slot is still in use until *both* the tail 63 // index has moved beyond it and typ has been set to nil. This 64 // is set to nil atomically by the consumer and read 65 // atomically by the producer. 66 vals []eface 67 } 68 69 type eface struct { 70 typ, val unsafe.Pointer 71 } 72 73 const dequeueBits = 32 74 75 // dequeueLimit is the maximum size of a poolDequeue. 76 // 77 // This must be at most (1<<dequeueBits)/2 because detecting fullness 78 // depends on wrapping around the ring buffer without wrapping around 79 // the index. We divide by 4 so this fits in an int on 32-bit. 80 const dequeueLimit = (1 << dequeueBits) / 4 81 82 // dequeueNil is used in poolDeqeue to represent interface{}(nil). 83 // Since we use nil to represent empty slots, we need a sentinel value 84 // to represent nil. 85 type dequeueNil *struct{} 86 87 func (d *poolDequeue) unpack(ptrs uint64) (head, tail uint32) { 88 const mask = 1<<dequeueBits - 1 89 head = uint32((ptrs >> dequeueBits) & mask) 90 tail = uint32(ptrs & mask) 91 return 92 } 93 94 func (d *poolDequeue) pack(head, tail uint32) uint64 { 95 const mask = 1<<dequeueBits - 1 96 return (uint64(head) << dequeueBits) | 97 uint64(tail&mask) 98 } 99 100 // PushHead adds val at the head of the queue. It returns false if the 101 // queue is full. It must only be called by a single producer. 102 func (d *poolDequeue) PushHead(val interface{}) bool { 103 ptrs := atomic.LoadUint64(&d.headTail) 104 head, tail := d.unpack(ptrs) 105 if (tail+uint32(len(d.vals)))&(1<<dequeueBits-1) == head { 106 // Queue is full. 107 return false 108 } 109 slot := &d.vals[head&uint32(len(d.vals)-1)] 110 111 // Check if the head slot has been released by popTail. 112 typ := atomic.LoadPointer(&slot.typ) 113 if typ != nil { 114 // Another goroutine is still cleaning up the tail, so 115 // the queue is actually still full. 116 return false 117 } 118 119 // The head slot is free, so we own it. 120 if val == nil { 121 val = dequeueNil(nil) 122 } 123 *(*interface{})(unsafe.Pointer(slot)) = val 124 125 // Increment head. This passes ownership of slot to popTail 126 // and acts as a store barrier for writing the slot. 127 atomic.AddUint64(&d.headTail, 1<<dequeueBits) 128 return true 129 } 130 131 // PopHead removes and returns the element at the head of the queue. 132 // It returns false if the queue is empty. It must only be called by a 133 // single producer. 134 func (d *poolDequeue) PopHead() (interface{}, bool) { 135 var slot *eface 136 for { 137 ptrs := atomic.LoadUint64(&d.headTail) 138 head, tail := d.unpack(ptrs) 139 if tail == head { 140 // Queue is empty. 141 return nil, false 142 } 143 144 // Confirm tail and decrement head. We do this before 145 // reading the value to take back ownership of this 146 // slot. 147 head-- 148 ptrs2 := d.pack(head, tail) 149 if atomic.CompareAndSwapUint64(&d.headTail, ptrs, ptrs2) { 150 // We successfully took back slot. 151 slot = &d.vals[head&uint32(len(d.vals)-1)] 152 break 153 } 154 } 155 156 val := *(*interface{})(unsafe.Pointer(slot)) 157 if val == dequeueNil(nil) { 158 val = nil 159 } 160 // Zero the slot. Unlike popTail, this isn't racing with 161 // pushHead, so we don't need to be careful here. 162 *slot = eface{} 163 return val, true 164 } 165 166 // PopTail removes and returns the element at the tail of the queue. 167 // It returns false if the queue is empty. It may be called by any 168 // number of consumers. 169 func (d *poolDequeue) PopTail() (interface{}, bool) { 170 var slot *eface 171 for { 172 ptrs := atomic.LoadUint64(&d.headTail) 173 head, tail := d.unpack(ptrs) 174 if tail == head { 175 // Queue is empty. 176 return nil, false 177 } 178 179 // Confirm head and tail (for our speculative check 180 // above) and increment tail. If this succeeds, then 181 // we own the slot at tail. 182 ptrs2 := d.pack(head, tail+1) 183 if atomic.CompareAndSwapUint64(&d.headTail, ptrs, ptrs2) { 184 // Success. 185 slot = &d.vals[tail&uint32(len(d.vals)-1)] 186 break 187 } 188 } 189 190 // We now own slot. 191 val := *(*interface{})(unsafe.Pointer(slot)) 192 if val == dequeueNil(nil) { 193 val = nil 194 } 195 196 // Tell pushHead that we're done with this slot. Zeroing the 197 // slot is also important so we don't leave behind references 198 // that could keep this object live longer than necessary. 199 // 200 // We write to val first and then publish that we're done with 201 // this slot by atomically writing to typ. 202 slot.val = nil 203 atomic.StorePointer(&slot.typ, nil) 204 // At this point pushHead owns the slot. 205 206 return val, true 207 } 208 209 // NewSPMCLockFreeQ new a SPMCLockFreeQ instance. 210 func NewSPMCLockFreeQ(n int) (SPMCLockFreeQ, error) { 211 if n&(n-1) != 0 { 212 return nil, errors.New("the size of pool must be a power of 2") 213 } 214 d := &poolDequeue{ 215 vals: make([]eface, n), 216 } 217 return d, nil 218 }