BluFedora Job System v1.0.0
This is a C++ job system library for use in game engines.
job_queue.hpp
Go to the documentation of this file.
1/******************************************************************************/
13/******************************************************************************/
14#ifndef JOB_QUEUE_HPP
15#define JOB_QUEUE_HPP
16
17#include "job_api.hpp" // PauseProcessor, JobAssert
18
19#include <algorithm> // copy_n
20#include <atomic> // atomic<T>
21#include <cstddef> // size_t
22#include <iterator> // make_move_iterator
23#include <mutex> // mutex
24#include <new> // hardware_destructive_interference_size, operator new
25#include <utility> // move
26
27namespace job
28{
29 static constexpr std::size_t k_FalseSharingPadSize = std::hardware_destructive_interference_size;
30
31 template<typename T>
33 {
34 public:
35 using size_type = std::size_t;
36
37 private:
38 std::mutex m_Lock;
44
45 public:
46 void Initialize(T* const memory_backing, const size_type capacity) noexcept
47 {
48 m_Data = memory_backing;
49 m_Capacity = capacity;
50 m_CapacityMask = capacity - 1;
51 m_WriteIndex = 0u;
52 m_Size = 0u;
53
54 JobAssert((m_Capacity & m_CapacityMask) == 0, "Capacity must be a power of 2.");
55 }
56
57 bool Push(const T& value)
58 {
59 const std::lock_guard<std::mutex> guard(m_Lock);
60 (void)guard;
61
62 if (m_Size == m_Capacity)
63 {
64 return false;
65 }
66
67 *elementAt(m_WriteIndex++) = value;
68 ++m_Size;
69
70 return true;
71 }
72
73 bool Pop(T* const out_value)
74 {
75 JobAssert(out_value != nullptr, "`out_value` cannot be a nullptr.");
76
77 const std::lock_guard<std::mutex> guard(m_Lock);
78 (void)guard;
79
80 if (m_Size == 0u)
81 {
82 return false;
83 }
84
85 *out_value = *elementAt(m_WriteIndex - m_Size);
86 --m_Size;
87
88 return true;
89 }
90
91 private:
92 size_type mask(const size_type raw_index) const noexcept
93 {
94 return raw_index & m_CapacityMask;
95 }
96
97 T* elementAt(const size_type raw_index) const noexcept
98 {
99 return m_Data + mask(raw_index);
100 }
101 };
102
103#define Job_CacheAlign alignas(k_FalseSharingPadSize)
104
105 // [https://www.youtube.com/watch?v=K3P_Lmq6pw0]
106 //
107 // Single Producer, Single Consumer Lockfree Queue
108 //
109 template<typename T>
111 {
112 public:
113 using size_type = std::size_t;
114 using atomic_size_type = std::atomic<size_type>;
115
116 private:
117 // Writer Thread
118
123
124 // Reader Thread
125
130
131 // Shared 'Immutable' State
132
136 unsigned char m_Padding4[k_FalseSharingPadSize - sizeof(m_Data) - sizeof(m_Capacity) - sizeof(m_CapacityMask)];
137
138 static_assert(atomic_size_type::is_always_lock_free, "Expected to be lockfree.");
139
140 public:
141 SPSCQueue() = default;
142 ~SPSCQueue() = default;
143
144 void Initialize(T* const memory_backing, const size_type capacity) noexcept
145 {
146 m_ProducerIndex.store(0, std::memory_order_relaxed);
148 m_ConsumerIndex.store(0, std::memory_order_relaxed);
150 m_Data = memory_backing;
151 m_Capacity = capacity;
152 m_CapacityMask = capacity - 1;
153
154 JobAssert((m_Capacity & m_CapacityMask) == 0, "Capacity must be a power of 2.");
155 }
156
157 bool Push(const T& value)
158 {
159 return PushLazy([&value](T* const destination) { ::new (destination) T(value); });
160 }
161
162 bool Pop(T* const out_value)
163 {
164 JobAssert(out_value != nullptr, "`out_value` cannot be a nullptr.");
165
166 return PopLazy([out_value](T&& value) { *out_value = std::move(value); });
167 }
168
169 template<typename CallbackFn>
170 bool PushLazy(CallbackFn&& callback)
171 {
172 const size_type write_index = m_ProducerIndex.load(std::memory_order_relaxed);
173
174 if (IsFull(write_index, m_CachedConsumerIndex))
175 {
176 m_CachedConsumerIndex = m_ConsumerIndex.load(std::memory_order_acquire);
177 if (IsFull(write_index, m_CachedConsumerIndex))
178 {
179 return false;
180 }
181 }
182
183 callback(ElementAt(write_index));
184 m_ProducerIndex.store(write_index + 1, std::memory_order_release);
185
186 return true;
187 }
188
189 template<typename CallbackFn>
190 bool PopLazy(CallbackFn&& callback)
191 {
192 const size_type read_index = m_ConsumerIndex.load(std::memory_order_relaxed);
193
194 if (IsEmpty(m_CachedProducerIndex, read_index))
195 {
196 m_CachedProducerIndex = m_ProducerIndex.load(std::memory_order_acquire);
197 if (IsEmpty(m_CachedProducerIndex, read_index))
198 {
199 return false;
200 }
201 }
202
203 T* const element = ElementAt(read_index);
204 callback(std::move(*element));
205 element->~T();
206 m_ConsumerIndex.store(read_index + 1, std::memory_order_release);
207
208 return true;
209 }
210
211 private:
212 bool IsFull(const size_type head, const size_type tail) const noexcept
213 {
214 return ((head + 1) & m_CapacityMask) == tail;
215 }
216
217 static bool IsEmpty(const size_type head, const size_type tail) noexcept
218 {
219 return head == tail;
220 }
221
222 T* ElementAt(const size_type index) const noexcept
223 {
224 return m_Data + (index & m_CapacityMask);
225 }
226 };
227
229 {
230 SUCCESS,
233 };
234
235 // [Dynamic Circular Work-Stealing Deque](https://www.dre.vanderbilt.edu/~schmidt/PDF/work-stealing-dequeue.pdf)
236 // [Correct and Efficient Work-Stealing for Weak Memory Models](https://fzn.fr/readings/ppopp13.pdf)
237 template<typename T>
239 {
240 private:
241 using AtomicT = std::atomic<T>;
242
243 static_assert(AtomicT::is_always_lock_free, "T Should be a small pointer-like type, expected to be lock-free when atomic.");
244
245 public:
246 using size_type = std::int64_t; // NOTE(SR): Must be signed for Pop to work correctly on empty queue.
247 using atomic_size_type = std::atomic<size_type>;
248
249 private:
253
254 // Shared 'Immutable' State
255
259
260 public:
261 SPMCDeque() = default;
262 ~SPMCDeque() = default;
263
264 void Initialize(AtomicT* const memory_backing, const size_type capacity) noexcept
265 {
266 m_ProducerIndex = 0;
267 m_ConsumerIndex = 0;
268 m_Data = memory_backing;
269 m_Capacity = capacity;
270 m_CapacityMask = capacity - 1;
271
272 JobAssert((m_Capacity & m_CapacityMask) == 0, "Capacity must be a power of 2.");
273 }
274
275 // NOTE(SR): Must be called by owning thread.
276
277 SPMCDequeStatus Push(const T& value)
278 {
279 const size_type write_index = m_ProducerIndex.load(std::memory_order_relaxed);
280 const size_type read_index = m_ConsumerIndex.load(std::memory_order_acquire);
281 const size_type size = write_index - read_index;
282
283 if (size > m_CapacityMask)
284 {
286 }
287
288 ElementAt(write_index)->store(value, std::memory_order_relaxed);
289
290 m_ProducerIndex.store(write_index + 1, std::memory_order_release);
291
293 }
294
295 SPMCDequeStatus Pop(T* const out_value)
296 {
297 const size_type producer_index = m_ProducerIndex.load(std::memory_order_relaxed) - 1;
298
299 // Reserve the slot at the producer end.
300 m_ProducerIndex.store(producer_index, std::memory_order_relaxed);
301
302 // The above store needs to happen before this next read
303 // to have consistent view of the buffer.
304 //
305 // `m_ProducerIndex` can only be written to by this thread
306 // so first reserve a slot then we read what the other threads have to say.
307 //
308 std::atomic_thread_fence(std::memory_order_seq_cst);
309
310 size_type consumer_index = m_ConsumerIndex.load(std::memory_order_relaxed);
311
312 if (consumer_index <= producer_index)
313 {
314 if (consumer_index == producer_index) // Only one item in queue
315 {
316 const bool successful_pop = m_ConsumerIndex.compare_exchange_strong(consumer_index, consumer_index + 1, std::memory_order_seq_cst, std::memory_order_relaxed);
317
318 if (successful_pop)
319 {
320 *out_value = ElementAt(producer_index)->load(std::memory_order_relaxed);
321 }
322
323 m_ProducerIndex.store(producer_index + 1, std::memory_order_relaxed);
325 }
326
327 *out_value = ElementAt(producer_index)->load(std::memory_order_relaxed);
329 }
330
331 // Empty Queue, so restore to canonical empty.
332 m_ProducerIndex.store(producer_index + 1, std::memory_order_seq_cst);
334 }
335
336 // NOTE(SR): Must be called by non owning thread.
337
338 SPMCDequeStatus Steal(T* const out_value)
339 {
340 size_type read_index = m_ConsumerIndex.load(std::memory_order_acquire);
341
342 // Must fully read `m_ConsumerIndex` before we read the producer owned `m_ProducerIndex`.
343 std::atomic_thread_fence(std::memory_order_seq_cst);
344
345 const size_type write_index = m_ProducerIndex.load(std::memory_order_acquire);
346
347 // if (next_read_index <= write_index)
348 if (read_index < write_index)
349 {
350 // Must load result before the CAS, since a push can happen concurrently right after the CAS.
351 T result = ElementAt(read_index)->load(std::memory_order_relaxed);
352
353 // Need strong memory ordering to read the element before the cas.
354 if (m_ConsumerIndex.compare_exchange_strong(read_index, read_index + 1, std::memory_order_seq_cst, std::memory_order_relaxed))
355 {
356 *out_value = std::move(result);
358 }
359
361 }
362
364 }
365
366 private:
367 AtomicT* ElementAt(const size_type index) const noexcept
368 {
369 return m_Data + (index & m_CapacityMask);
370 }
371 };
372
373 // https://www.youtube.com/watch?v=_qaKkHuHYE0&ab_channel=CppCon
375 {
376 public:
377 using size_type = std::size_t;
378 using atomic_size_type = std::atomic<size_type>;
379 using value_type = unsigned char; // byte
380
381 private:
383 {
386 };
387
388 private:
397 unsigned char m_Padding2[k_FalseSharingPadSize - sizeof(m_Queue) - sizeof(m_Capacity)];
398
399 public:
400 MPMCQueue() = default;
401 ~MPMCQueue() = default;
402
403 void Initialize(value_type* const memory_backing, const size_type capacity) noexcept
404 {
405 m_ProducerPending.store(0, std::memory_order_relaxed);
406 m_ProducerCommited.store(0, std::memory_order_relaxed);
407 m_ConsumerPending.store(0, std::memory_order_relaxed);
408 m_ConsumerCommited.store(0, std::memory_order_relaxed);
409 m_Queue = memory_backing;
410 m_Capacity = capacity;
411 }
412
413 //
414
415 bool PushExact(const value_type* elements, const size_type num_elements)
416 {
417 return PushImpl<true>(elements, num_elements) != 0u;
418 }
419
420 size_type PushUpTo(const value_type* elements, const size_type num_elements)
421 {
422 return PushImpl<false>(elements, num_elements);
423 }
424
425 bool PopExact(value_type* out_elements, const size_type num_elements)
426 {
427 return PopImpl<true>(out_elements, num_elements) != 0u;
428 }
429
430 size_type PopUpTo(value_type* out_elements, const size_type num_elements)
431 {
432 return PopImpl<false>(out_elements, num_elements);
433 }
434
435 private:
436 template<bool allOrNothing>
437 size_type PushImpl(const value_type* elements, const size_type num_elements)
438 {
439 IndexRange range;
440 if (RequestWriteRange<allOrNothing>(&range, num_elements))
441 {
442 const size_type written_elements = WriteElements(elements, range);
443 Commit(&m_ProducerCommited, range);
444 return written_elements;
445 }
446
447 return 0u;
448 }
449
450 template<bool allOrNothing>
451 size_type PopImpl(value_type* out_elements, const size_type num_elements)
452 {
453 IndexRange range;
454 if (RequestPopRange<allOrNothing>(&range, num_elements))
455 {
456 const size_type read_elements = ReadElements(out_elements, range);
457 Commit(&m_ConsumerCommited, range);
458 return read_elements;
459 }
460
461 return 0u;
462 }
463
464 template<bool allOrNothing>
465 bool RequestWriteRange(IndexRange* out_range, const size_type num_items)
466 {
467 size_type old_head, new_head;
468
469 old_head = m_ProducerPending.load(std::memory_order_relaxed);
470 do
471 {
472 const size_type tail = m_ConsumerCommited.load(std::memory_order_acquire);
473
474 size_type capacity_left = Distance(old_head, tail);
475 if constexpr (allOrNothing)
476 {
477 if (capacity_left < num_items)
478 {
479 capacity_left = 0;
480 }
481 }
482
483 if (capacity_left == 0)
484 {
485 return false;
486 }
487
488 const size_type num_element_to_write = capacity_left < num_items ? capacity_left : num_items;
489
490 new_head = old_head + num_element_to_write;
491
492 } while (!m_ProducerPending.compare_exchange_weak(old_head, new_head, std::memory_order_relaxed, std::memory_order_relaxed));
493
494 *out_range = {old_head, new_head};
495 return true;
496 }
497
498 template<bool allOrNothing>
499 bool RequestPopRange(IndexRange* out_range, const size_type num_items)
500 {
501 size_type old_tail, new_tail;
502
503 old_tail = m_ConsumerPending.load(std::memory_order_relaxed);
504 do
505 {
506 const size_type head = m_ProducerCommited.load(std::memory_order_acquire);
507 const size_type distance = Distance(head, old_tail);
508
509 size_t capacity_left = (m_Capacity - distance);
510 if constexpr (allOrNothing)
511 {
512 if (capacity_left < num_items)
513 {
514 capacity_left = 0;
515 }
516 }
517
518 if (!capacity_left)
519 {
520 return false;
521 }
522
523 const size_type num_element_to_read = capacity_left < num_items ? capacity_left : num_items;
524
525 new_tail = old_tail + num_element_to_read;
526
527 } while (!m_ConsumerPending.compare_exchange_weak(old_tail, new_tail, std::memory_order_relaxed, std::memory_order_relaxed));
528
529 *out_range = {old_tail, new_tail};
530 return true;
531 }
532
533 size_type WriteElements(const value_type* const elements, const IndexRange range)
534 {
535 const size_type real_start = range.start % m_Capacity;
536 const size_type write_size = Distance(real_start, range.end % m_Capacity);
537 const size_type capacity_before_split = m_Capacity - real_start;
538 const size_type num_items_before_split = write_size < capacity_before_split ? write_size : capacity_before_split;
539 const size_type num_items_after_split = write_size - num_items_before_split;
540
541 std::copy_n(elements + 0u, num_items_before_split, m_Queue + real_start);
542 std::copy_n(elements + num_items_before_split, num_items_after_split, m_Queue + 0u);
543
544 return write_size;
545 }
546
547 size_type ReadElements(value_type* const out_elements, const IndexRange range) const
548 {
549 const size_type real_start = range.start % m_Capacity;
550 const size_type read_size = Distance(real_start, range.end % m_Capacity);
551 const size_type capacity_before_split = m_Capacity - real_start;
552 const size_type num_items_before_split = read_size < capacity_before_split ? read_size : capacity_before_split;
553 const size_type num_items_after_split = read_size - num_items_before_split;
554
555 std::copy_n(std::make_move_iterator(m_Queue + real_start), num_items_before_split, out_elements + 0u);
556 std::copy_n(std::make_move_iterator(m_Queue + 0u), num_items_after_split, out_elements + num_items_before_split);
557
558 return read_size;
559 }
560
561 void Commit(atomic_size_type* commit, const IndexRange range) const
562 {
563 size_type start_copy;
564 while (!commit->compare_exchange_weak(
565 start_copy = range.start,
566 range.end,
567 std::memory_order_release,
568 std::memory_order_relaxed))
569 {
571 }
572 }
573
574 size_type Distance(const size_type a, const size_type b) const
575 {
576 return (b > a) ? (b - a) : m_Capacity - a + b;
577 }
578 };
579
580#undef Job_CacheAlign
581}
582
583#endif // JOB_QUEUE_HPP
584
585/******************************************************************************/
586/*
587 MIT License
588
589 Copyright (c) 2024-2026 Shareef Abdoul-Raheem
590
591 Permission is hereby granted, free of charge, to any person obtaining a copy
592 of this software and associated documentation files (the "Software"), to deal
593 in the Software without restriction, including without limitation the rights
594 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
595 copies of the Software, and to permit persons to whom the Software is
596 furnished to do so, subject to the following conditions:
597
598 The above copyright notice and this permission notice shall be included in all
599 copies or substantial portions of the Software.
600
601 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
602 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
603 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
604 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
605 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
606 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
607 SOFTWARE.
608*/
609/******************************************************************************/
size_type m_Size
Definition: job_queue.hpp:43
size_type m_CapacityMask
Definition: job_queue.hpp:41
T * elementAt(const size_type raw_index) const noexcept
Definition: job_queue.hpp:97
bool Push(const T &value)
Definition: job_queue.hpp:57
size_type mask(const size_type raw_index) const noexcept
Definition: job_queue.hpp:92
std::size_t size_type
Definition: job_queue.hpp:35
std::mutex m_Lock
Definition: job_queue.hpp:38
bool Pop(T *const out_value)
Definition: job_queue.hpp:73
size_type m_Capacity
Definition: job_queue.hpp:40
size_type m_WriteIndex
Definition: job_queue.hpp:42
void Initialize(T *const memory_backing, const size_type capacity) noexcept
Definition: job_queue.hpp:46
size_type m_Capacity
Definition: job_queue.hpp:396
std::atomic< size_type > atomic_size_type
Definition: job_queue.hpp:378
value_type * m_Queue
Definition: job_queue.hpp:395
size_type PushImpl(const value_type *elements, const size_type num_elements)
Definition: job_queue.hpp:437
atomic_size_type m_ConsumerPending
Definition: job_queue.hpp:392
bool PopExact(value_type *out_elements, const size_type num_elements)
Definition: job_queue.hpp:425
bool RequestPopRange(IndexRange *out_range, const size_type num_items)
Definition: job_queue.hpp:499
size_type PopUpTo(value_type *out_elements, const size_type num_elements)
Definition: job_queue.hpp:430
size_type Distance(const size_type a, const size_type b) const
Definition: job_queue.hpp:574
unsigned char m_Padding0[k_FalseSharingPadSize - sizeof(atomic_size_type) *2]
Definition: job_queue.hpp:391
size_type ReadElements(value_type *const out_elements, const IndexRange range) const
Definition: job_queue.hpp:547
bool PushExact(const value_type *elements, const size_type num_elements)
Definition: job_queue.hpp:415
atomic_size_type m_ConsumerCommited
Definition: job_queue.hpp:393
MPMCQueue()=default
unsigned char m_Padding1[k_FalseSharingPadSize - sizeof(atomic_size_type) *2]
Definition: job_queue.hpp:394
~MPMCQueue()=default
size_type PopImpl(value_type *out_elements, const size_type num_elements)
Definition: job_queue.hpp:451
unsigned char value_type
Definition: job_queue.hpp:379
void Initialize(value_type *const memory_backing, const size_type capacity) noexcept
Definition: job_queue.hpp:403
void Commit(atomic_size_type *commit, const IndexRange range) const
Definition: job_queue.hpp:561
std::size_t size_type
Definition: job_queue.hpp:377
atomic_size_type m_ProducerCommited
Definition: job_queue.hpp:390
atomic_size_type m_ProducerPending
Definition: job_queue.hpp:389
size_type WriteElements(const value_type *const elements, const IndexRange range)
Definition: job_queue.hpp:533
bool RequestWriteRange(IndexRange *out_range, const size_type num_items)
Definition: job_queue.hpp:465
size_type PushUpTo(const value_type *elements, const size_type num_elements)
Definition: job_queue.hpp:420
unsigned char m_Padding2[k_FalseSharingPadSize - sizeof(m_Queue) - sizeof(m_Capacity)]
Definition: job_queue.hpp:397
std::atomic< T > AtomicT
Definition: job_queue.hpp:241
~SPMCDeque()=default
SPMCDequeStatus Pop(T *const out_value)
Definition: job_queue.hpp:295
void Initialize(AtomicT *const memory_backing, const size_type capacity) noexcept
Definition: job_queue.hpp:264
std::atomic< size_type > atomic_size_type
Definition: job_queue.hpp:247
std::int64_t size_type
Definition: job_queue.hpp:246
AtomicT * ElementAt(const size_type index) const noexcept
Definition: job_queue.hpp:367
atomic_size_type m_ConsumerIndex
Definition: job_queue.hpp:251
SPMCDeque()=default
unsigned char m_Padding0[k_FalseSharingPadSize - sizeof(m_ProducerIndex) - sizeof(m_ConsumerIndex)]
Definition: job_queue.hpp:252
size_type m_CapacityMask
Definition: job_queue.hpp:258
size_type m_Capacity
Definition: job_queue.hpp:257
SPMCDequeStatus Push(const T &value)
Definition: job_queue.hpp:277
atomic_size_type m_ProducerIndex
Definition: job_queue.hpp:250
SPMCDequeStatus Steal(T *const out_value)
Definition: job_queue.hpp:338
AtomicT * m_Data
Definition: job_queue.hpp:256
unsigned char m_Padding1[k_FalseSharingPadSize - sizeof(m_CachedConsumerIndex)]
Definition: job_queue.hpp:122
size_type m_CachedProducerIndex
Definition: job_queue.hpp:128
unsigned char m_Padding3[k_FalseSharingPadSize - sizeof(m_CachedProducerIndex)]
Definition: job_queue.hpp:129
size_type m_Capacity
Definition: job_queue.hpp:134
~SPSCQueue()=default
bool Push(const T &value)
Definition: job_queue.hpp:157
std::size_t size_type
Definition: job_queue.hpp:113
bool IsFull(const size_type head, const size_type tail) const noexcept
Definition: job_queue.hpp:212
size_type m_CachedConsumerIndex
Definition: job_queue.hpp:121
SPSCQueue()=default
size_type m_CapacityMask
Definition: job_queue.hpp:135
bool Pop(T *const out_value)
Definition: job_queue.hpp:162
std::atomic< size_type > atomic_size_type
Definition: job_queue.hpp:114
unsigned char m_Padding0[k_FalseSharingPadSize - sizeof(m_ProducerIndex)]
Definition: job_queue.hpp:120
void Initialize(T *const memory_backing, const size_type capacity) noexcept
Definition: job_queue.hpp:144
atomic_size_type m_ConsumerIndex
Definition: job_queue.hpp:126
unsigned char m_Padding2[k_FalseSharingPadSize - sizeof(m_ConsumerIndex)]
Definition: job_queue.hpp:127
static bool IsEmpty(const size_type head, const size_type tail) noexcept
Definition: job_queue.hpp:217
bool PushLazy(CallbackFn &&callback)
Definition: job_queue.hpp:170
bool PopLazy(CallbackFn &&callback)
Definition: job_queue.hpp:190
atomic_size_type m_ProducerIndex
Definition: job_queue.hpp:119
unsigned char m_Padding4[k_FalseSharingPadSize - sizeof(m_Data) - sizeof(m_Capacity) - sizeof(m_CapacityMask)]
Definition: job_queue.hpp:136
T * ElementAt(const size_type index) const noexcept
Definition: job_queue.hpp:222
API for a multi-threading job system.
#define JobAssert(expr, msg)
Definition: job_api.hpp:32
#define Job_CacheAlign
Definition: job_queue.hpp:103
Definition: job_api.hpp:38
static constexpr std::size_t k_FalseSharingPadSize
Definition: job_queue.hpp:29
void PauseProcessor() noexcept
CPU pause instruction to indicate when you are in a spin wait loop.
SPMCDequeStatus
Definition: job_queue.hpp:229
@ FAILED_RACE
Returned from Pop and Steal.
@ FAILED_SIZE
Returned from Push, Pop and Steal.
@ SUCCESS
Returned from Push, Pop and Steal.