├── README.md ├── queue.go └── queue_test.go /README.md: -------------------------------------------------------------------------------- 1 | # Queue 2 | 3 | [![GoDoc](https://godoc.org/github.com/sheerun/queue?status.svg)](https://godoc.org/github.com/sheerun/queue) 4 | [![Release](https://img.shields.io/github/release/sheerun/queue.svg)](https://github.com/sheerun/queue/releases/latest) 5 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.txt) 6 | 7 | Lightweight, tested, performant, thread-safe, blocking FIFO queue based on auto-resizing circular buffer. 8 | 9 | ## Usage 10 | 11 | ```go 12 | package main 13 | 14 | import ( 15 | "fmt" 16 | "sync" 17 | "time" 18 | 19 | "github.com/sheerun/queue" 20 | ) 21 | 22 | func main() { 23 | q := queue.New() 24 | var wg sync.WaitGroup 25 | wg.Add(2) 26 | 27 | // Worker 1 28 | go func() { 29 | for i := 0; i < 500; i++ { 30 | item := q.Pop() 31 | fmt.Printf("%v\n", item) 32 | time.Sleep(10 * time.Millisecond) 33 | } 34 | wg.Done() 35 | }() 36 | 37 | // Worker 2 38 | go func() { 39 | for i := 0; i < 500; i++ { 40 | item := q.Pop() 41 | fmt.Printf("%v\n", item) 42 | time.Sleep(10 * time.Millisecond) 43 | } 44 | wg.Done() 45 | }() 46 | 47 | for i := 0; i < 1000; i++ { 48 | q.Append(i) 49 | } 50 | 51 | wg.Wait() 52 | } 53 | 54 | ``` 55 | 56 | ## License 57 | 58 | MIT 59 | -------------------------------------------------------------------------------- /queue.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "math/rand" 5 | "sync" 6 | ) 7 | 8 | const minQueueLen = 32 9 | 10 | type Queue struct { 11 | items map[int64]interface{} 12 | ids map[interface{}]int64 13 | buf []int64 14 | head, tail, count int 15 | mutex *sync.Mutex 16 | notEmpty *sync.Cond 17 | // You can subscribe to this channel to know whether queue is not empty 18 | NotEmpty chan struct{} 19 | } 20 | 21 | func New() *Queue { 22 | q := &Queue{ 23 | items: make(map[int64]interface{}), 24 | ids: make(map[interface{}]int64), 25 | buf: make([]int64, minQueueLen), 26 | mutex: &sync.Mutex{}, 27 | NotEmpty: make(chan struct{}, 1), 28 | } 29 | 30 | q.notEmpty = sync.NewCond(q.mutex) 31 | 32 | return q 33 | } 34 | 35 | // Removes all elements from queue 36 | func (q *Queue) Clean() { 37 | q.mutex.Lock() 38 | defer q.mutex.Unlock() 39 | 40 | q.items = make(map[int64]interface{}) 41 | q.ids = make(map[interface{}]int64) 42 | q.buf = make([]int64, minQueueLen) 43 | q.tail = 0 44 | q.head = 0 45 | q.count = 0 46 | } 47 | 48 | // Returns the number of elements in queue 49 | func (q *Queue) Length() int { 50 | q.mutex.Lock() 51 | defer q.mutex.Unlock() 52 | 53 | return len(q.items) 54 | } 55 | 56 | // resizes the queue to fit exactly twice its current contents 57 | // this can result in shrinking if the queue is less than half-full 58 | func (q *Queue) resize() { 59 | newCount := q.count << 1 60 | 61 | if q.count < 2<<18 { 62 | newCount = newCount << 2 63 | } 64 | 65 | newBuf := make([]int64, newCount) 66 | 67 | if q.tail > q.head { 68 | copy(newBuf, q.buf[q.head:q.tail]) 69 | } else { 70 | n := copy(newBuf, q.buf[q.head:]) 71 | copy(newBuf[n:], q.buf[:q.tail]) 72 | } 73 | 74 | q.head = 0 75 | q.tail = q.count 76 | q.buf = newBuf 77 | } 78 | 79 | func (q *Queue) notify() { 80 | if len(q.items) > 0 { 81 | select { 82 | case q.NotEmpty <- struct{}{}: 83 | default: 84 | } 85 | } 86 | } 87 | 88 | // Adds one element at the back of the queue 89 | func (q *Queue) Append(elem interface{}) { 90 | q.mutex.Lock() 91 | defer q.mutex.Unlock() 92 | 93 | if q.count == len(q.buf) { 94 | q.resize() 95 | } 96 | 97 | id := q.newId() 98 | q.items[id] = elem 99 | q.ids[elem] = id 100 | q.buf[q.tail] = id 101 | // bitwise modulus 102 | q.tail = (q.tail + 1) & (len(q.buf) - 1) 103 | q.count++ 104 | 105 | q.notify() 106 | 107 | if q.count == 1 { 108 | q.notEmpty.Broadcast() 109 | } 110 | } 111 | 112 | func (q *Queue) newId() int64 { 113 | for { 114 | id := rand.Int63() 115 | _, ok := q.items[id] 116 | if id != 0 && !ok { 117 | return id 118 | } 119 | } 120 | } 121 | 122 | // Adds one element at the front of queue 123 | func (q *Queue) Prepend(elem interface{}) { 124 | q.mutex.Lock() 125 | defer q.mutex.Unlock() 126 | 127 | if q.count == len(q.buf) { 128 | q.resize() 129 | } 130 | 131 | q.head = (q.head - 1) & (len(q.buf) - 1) 132 | id := q.newId() 133 | q.items[id] = elem 134 | q.ids[elem] = id 135 | q.buf[q.head] = id 136 | // bitwise modulus 137 | q.count++ 138 | 139 | q.notify() 140 | 141 | if q.count == 1 { 142 | q.notEmpty.Broadcast() 143 | } 144 | } 145 | 146 | // Previews element at the front of queue 147 | func (q *Queue) Front() interface{} { 148 | q.mutex.Lock() 149 | defer q.mutex.Unlock() 150 | 151 | id := q.buf[q.head] 152 | if id != 0 { 153 | return q.items[id] 154 | } 155 | return nil 156 | } 157 | 158 | // Previews element at the back of queue 159 | func (q *Queue) Back() interface{} { 160 | q.mutex.Lock() 161 | defer q.mutex.Unlock() 162 | id := q.buf[(q.tail-1)&(len(q.buf)-1)] 163 | if id != 0 { 164 | return q.items[id] 165 | } 166 | return nil 167 | } 168 | 169 | func (q *Queue) pop() int64 { 170 | for { 171 | if q.count <= 0 { 172 | q.notEmpty.Wait() 173 | } 174 | 175 | // I have no idea why, but sometimes it's less than 0 176 | if q.count > 0 { 177 | break 178 | } 179 | } 180 | 181 | id := q.buf[q.head] 182 | q.buf[q.head] = 0 183 | 184 | // bitwise modulus 185 | q.head = (q.head + 1) & (len(q.buf) - 1) 186 | q.count-- 187 | if len(q.buf) > minQueueLen && (q.count<<1) == len(q.buf) { 188 | q.resize() 189 | } 190 | 191 | return id 192 | } 193 | 194 | // Pop removes and returns the element from the front of the queue. 195 | // If the queue is empty, it will block 196 | func (q *Queue) Pop() interface{} { 197 | q.mutex.Lock() 198 | defer q.mutex.Unlock() 199 | 200 | for { 201 | id := q.pop() 202 | 203 | item, ok := q.items[id] 204 | 205 | if ok { 206 | delete(q.ids, item) 207 | delete(q.items, id) 208 | q.notify() 209 | return item 210 | } 211 | } 212 | } 213 | 214 | // Removes one element from the queue 215 | func (q *Queue) Remove(elem interface{}) bool { 216 | q.mutex.Lock() 217 | defer q.mutex.Unlock() 218 | 219 | id, ok := q.ids[elem] 220 | if !ok { 221 | return false 222 | } 223 | delete(q.ids, elem) 224 | delete(q.items, id) 225 | return true 226 | } 227 | -------------------------------------------------------------------------------- /queue_test.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestQueueSimple(t *testing.T) { 10 | q := New() 11 | 12 | for i := 0; i < minQueueLen; i++ { 13 | q.Append(i) 14 | } 15 | for i := 0; i < minQueueLen; i++ { 16 | x := q.Pop() 17 | if x != i { 18 | t.Error("remove", i, "had value", x) 19 | } 20 | } 21 | } 22 | 23 | func TestQueueSimplePrepend(t *testing.T) { 24 | q := New() 25 | 26 | for i := 0; i < minQueueLen; i++ { 27 | q.Prepend(i) 28 | } 29 | for i := minQueueLen - 1; i >= 0; i-- { 30 | x := q.Pop() 31 | if x != i { 32 | t.Error("remove", i, "had value", x) 33 | } 34 | } 35 | } 36 | 37 | func TestQueueManual(t *testing.T) { 38 | q := New() 39 | 40 | q.Append(1) 41 | q.Append(2) 42 | q.Prepend(4) 43 | 44 | if q.Pop() != 4 { 45 | t.Error("Invalid element") 46 | } 47 | 48 | q.Prepend(3) 49 | 50 | if q.Pop() != 3 { 51 | t.Error("Invalid element") 52 | } 53 | 54 | if q.Pop() != 1 { 55 | t.Error("Invalid element") 56 | } 57 | 58 | if q.Pop() != 2 { 59 | t.Error("Invalid element") 60 | } 61 | } 62 | 63 | func TestQueueWrapping(t *testing.T) { 64 | q := New() 65 | 66 | for i := 0; i < minQueueLen; i++ { 67 | q.Append(i) 68 | } 69 | for i := 0; i < 3; i++ { 70 | q.Pop() 71 | q.Append(minQueueLen + i) 72 | } 73 | 74 | for i := 0; i < minQueueLen; i++ { 75 | q.Pop() 76 | } 77 | } 78 | 79 | func TestQueueWrappingPrepend(t *testing.T) { 80 | q := New() 81 | 82 | for i := 0; i < minQueueLen; i++ { 83 | q.Prepend(i) 84 | } 85 | for i := 0; i < 3; i++ { 86 | q.Pop() 87 | q.Prepend(minQueueLen + i) 88 | } 89 | 90 | for i := 0; i < minQueueLen; i++ { 91 | q.Pop() 92 | } 93 | } 94 | 95 | func TestQueueThreadSafety(t *testing.T) { 96 | q := New() 97 | 98 | var wg sync.WaitGroup 99 | 100 | wg.Add(2) 101 | 102 | go func() { 103 | for i := 0; i < 10000; i++ { 104 | q.Append(i) 105 | } 106 | wg.Done() 107 | }() 108 | 109 | go func() { 110 | for i := 0; i < 10000; i++ { 111 | if q.Pop() != i { 112 | t.Errorf("Invalid returned index: %d", i) 113 | wg.Done() 114 | return 115 | } 116 | } 117 | wg.Done() 118 | }() 119 | 120 | wg.Wait() 121 | } 122 | 123 | func TestQueueThreadSafety2(t *testing.T) { 124 | q := New() 125 | 126 | var wg sync.WaitGroup 127 | 128 | wg.Add(2) 129 | 130 | go func() { 131 | for i := 0; i < 10000; i++ { 132 | q.Append(i) 133 | q.Prepend(i) 134 | } 135 | wg.Done() 136 | }() 137 | 138 | go func() { 139 | for i := 0; i < 20000; i++ { 140 | q.Pop() 141 | } 142 | wg.Done() 143 | }() 144 | 145 | wg.Wait() 146 | } 147 | 148 | func TestQueueThreadSafety3(t *testing.T) { 149 | q := New() 150 | 151 | var wg sync.WaitGroup 152 | 153 | wg.Add(10000) 154 | 155 | for i := 0; i < 5000; i++ { 156 | go func() { 157 | q.Append(i) 158 | wg.Done() 159 | }() 160 | } 161 | 162 | for i := 0; i < 5000; i++ { 163 | go func() { 164 | q.Pop() 165 | wg.Done() 166 | }() 167 | } 168 | 169 | wg.Wait() 170 | } 171 | 172 | func TestQueueLength(t *testing.T) { 173 | q := New() 174 | 175 | if q.Length() != 0 { 176 | t.Error("empty queue length not 0") 177 | } 178 | 179 | for i := 0; i < 1000; i++ { 180 | q.Append(i) 181 | if q.Length() != i+1 { 182 | t.Error("adding: queue with", i, "elements has length", q.Length()) 183 | } 184 | } 185 | for i := 0; i < 1000; i++ { 186 | q.Pop() 187 | if q.Length() != 1000-i-1 { 188 | t.Error("removing: queue with", 1000-i-i, "elements has length", q.Length()) 189 | } 190 | } 191 | } 192 | 193 | func TestQueueBlocking(t *testing.T) { 194 | q := New() 195 | 196 | var wg sync.WaitGroup 197 | 198 | wg.Add(1) 199 | go func() { 200 | q.Append(1) 201 | time.Sleep(1 * time.Second) 202 | q.Append(2) 203 | wg.Done() 204 | }() 205 | 206 | item := q.Pop() 207 | if item != 1 { 208 | t.Error("Returned invalid 1 element") 209 | } 210 | item2 := q.Pop() 211 | if item2 != 2 { 212 | t.Error("Returned invalid 2 element") 213 | } 214 | 215 | wg.Wait() 216 | } 217 | 218 | func assertPanics(t *testing.T, name string, f func()) { 219 | defer func() { 220 | if r := recover(); r == nil { 221 | t.Errorf("%s: didn't panic as expected", name) 222 | } 223 | }() 224 | 225 | f() 226 | } 227 | 228 | func TestFront(t *testing.T) { 229 | q := New() 230 | 231 | q.Append(1) 232 | q.Append(2) 233 | 234 | if q.Front() != 1 { 235 | t.Error("There should be 1 on front") 236 | } 237 | 238 | q.Pop() 239 | 240 | if q.Front() != 2 { 241 | t.Error("There should be 2 on front") 242 | } 243 | } 244 | 245 | func TestFrontEmpty(t *testing.T) { 246 | q := New() 247 | 248 | if q.Front() != nil { 249 | t.Error("There should be nil") 250 | } 251 | 252 | q.Append(1) 253 | q.Append(2) 254 | q.Prepend(3) 255 | q.Pop() 256 | q.Pop() 257 | q.Pop() 258 | 259 | if q.Front() != nil { 260 | t.Error("There should be nil") 261 | } 262 | } 263 | 264 | func TestBackEmpty(t *testing.T) { 265 | q := New() 266 | 267 | if q.Back() != nil { 268 | t.Error("There should be nil") 269 | } 270 | 271 | q.Append(1) 272 | q.Append(2) 273 | q.Prepend(3) 274 | q.Pop() 275 | q.Pop() 276 | q.Pop() 277 | 278 | if q.Back() != nil { 279 | t.Error("There should be nil") 280 | } 281 | } 282 | 283 | func TestBack(t *testing.T) { 284 | q := New() 285 | 286 | q.Append(1) 287 | q.Append(2) 288 | 289 | if q.Back() != 2 { 290 | t.Errorf("There should be 2 on back, there is %v", q.Back()) 291 | } 292 | 293 | q.Pop() 294 | 295 | if q.Back() != 2 { 296 | t.Errorf("There should be 2 on back, there is %v", q.Back()) 297 | } 298 | } 299 | 300 | func TestRemove(t *testing.T) { 301 | q := New() 302 | 303 | q.Append(1) 304 | q.Append(2) 305 | q.Append(3) 306 | q.Remove(2) 307 | 308 | if q.Length() != 2 { 309 | t.Errorf("Queue length should be 2, it is %d", q.Length()) 310 | } 311 | 312 | p := q.Pop() 313 | if p != 1 { 314 | t.Errorf("There should be 1 on pop, there is %v", p) 315 | } 316 | 317 | p = q.Pop() 318 | if p != 3 { 319 | t.Errorf("There should be 3 on pop, there is %v", p) 320 | } 321 | } 322 | 323 | func TestTestQueueClean(t *testing.T) { 324 | q := New() 325 | 326 | q.Append(4) 327 | q.Append(6) 328 | q.Clean() 329 | 330 | q.Append(1) 331 | q.Append(2) 332 | q.Append(3) 333 | q.Remove(2) 334 | 335 | if q.Length() != 2 { 336 | t.Errorf("Queue length should be 2, it is %d", q.Length()) 337 | } 338 | 339 | p := q.Pop() 340 | if p != 1 { 341 | t.Errorf("There should be 1 on pop, there is %v", p) 342 | } 343 | 344 | p = q.Pop() 345 | if p != 3 { 346 | t.Errorf("There should be 3 on pop, there is %v", p) 347 | } 348 | } 349 | 350 | func TestTestQueueClean2(t *testing.T) { 351 | q := New() 352 | 353 | for i := 0; i < 50; i++ { 354 | q.Append(i) 355 | } 356 | 357 | q.Clean() 358 | 359 | for i := 0; i < 50; i++ { 360 | q.Append(i) 361 | } 362 | } 363 | 364 | // General warning: Go's benchmark utility (go test -bench .) increases the number of 365 | // iterations until the benchmarks take a reasonable amount of time to run; memory usage 366 | // is *NOT* considered. On my machine, these benchmarks hit around ~1GB before they've had 367 | // enough, but if you have less than that available and start swapping, then all bets are off. 368 | 369 | func BenchmarkQueueSerial(b *testing.B) { 370 | q := New() 371 | b.ResetTimer() 372 | for i := 0; i < b.N; i++ { 373 | q.Append(i) 374 | } 375 | for i := 0; i < b.N; i++ { 376 | q.Pop() 377 | } 378 | } 379 | 380 | func BenchmarkQueueTickTock(b *testing.B) { 381 | q := New() 382 | for i := 0; i < b.N; i++ { 383 | q.Append(i) 384 | q.Pop() 385 | } 386 | } 387 | --------------------------------------------------------------------------------