CX Framework
Cross-platform C utility framework
Loading...
Searching...
No Matches
PrQueue

Data Structures

struct  PrQueue
 

Typedefs

typedef enum PrqGrowthEnum PrqGrowth
 How much a dynamic PrQueue grows or shrinks by when it resizes.
 
typedef struct PrQueue PrQueue
 

Enumerations

enum  PrqGrowthEnum {
  PRQ_Grow_None = 1 , PRQ_Grow_25 , PRQ_Grow_50 , PRQ_Grow_100 ,
  PRQ_Grow_150 , PRQ_Grow_200
}
 How much a dynamic PrQueue grows or shrinks by when it resizes. More...
 

Functions

void prqInitFixed (PrQueue *prq, uint32 sz)
 
void prqInitDynamic (PrQueue *prq, uint32 minsz, uint32 targetsz, uint32 maxsz, PrqGrowth growth, PrqGrowth shrink)
 
bool prqDestroy (PrQueue *prq)
 
bool prqPush (PrQueue *prq, void *ptr)
 
void * prqPop (PrQueue *prq)
 
bool prqCollect (PrQueue *prq)
 
uint32 prqCount (PrQueue *prq)
 
void * prqPeek (PrQueue *prq, uint32 n)
 

Detailed Description

A thread-safe, lock-free, optionally growable ring buffer of pointers. Multiple threads can push and pop at the same time.

PrQueue is low-level plumbing for building other concurrent structures, such as containers or a work queue, rather than a general-purpose collection to reach for directly. It moves only raw void* pointers - it never dereferences, allocates, frees, or copies whatever they point to. All lifetime management of the pointed-to data is the caller's responsibility.

NULL may never be pushed into the queue. prqPop() uses NULL as the "queue is empty" sentinel, so inserting one is an error.

Pushing a pointer transfers ownership of it to the queue - don't touch it again after a successful prqPush(). Popping transfers ownership back to the caller.

Fixed vs. dynamic queues

prqInitFixed(&q, 1024); // fixed capacity; push fails when full
// or, a queue that grows and shrinks between bounds:
prqInitDynamic(&q2, 64, 1024, 65536, PRQ_Grow_100, PRQ_Grow_50);
void prqInitDynamic(PrQueue *prq, uint32 minsz, uint32 targetsz, uint32 maxsz, PrqGrowth growth, PrqGrowth shrink)
void prqInitFixed(PrQueue *prq, uint32 sz)
@ PRQ_Grow_100
Resize by 100% (default)
Definition prqueue.h:84
@ PRQ_Grow_50
Resize by 50%.
Definition prqueue.h:83

A fixed queue never grows, and prqPush() simply fails when it's full. A dynamic queue grows toward its target size under load and shrinks back within its bounds, but every push and pop on a dynamic queue pays extra atomic bookkeeping to guard against a segment being freed out from under it, often close to double the atomic operations of a fixed queue doing the same work. Prefer a fixed queue whenever there's a defensible upper bound on depth; reach for dynamic only when the depth genuinely can't be bounded.

Lock-free guarantees

Pushing and popping are lock-free in the classical sense: a thread that suspends or terminates in the middle of an operation cannot corrupt the queue or permanently block other threads, though it can cost performance until it clears.

Garbage collection (prqCollect()) is the one exception. It reclaims buffer segments that were retired when a dynamic queue grew, and it does use a lock - but that lock never blocks the caller: if it can't be acquired, prqCollect() returns immediately instead of waiting. Call it opportunistically at natural idle points, such as a consumer thread about to go to sleep. GC is not required for correctness; a queue that never runs GC keeps working, it just holds on to retired segments and wastes memory after growth events. A thread that stalls in the middle of a push also blocks GC from pruning until it clears, for the same reason. Fixed queues never grow, so they never need GC.

Ordering guarantees

Pushes from a single thread are popped in order, as long as a single thread (not necessarily the same one) pops them sequentially. Across multiple threads, ordering is only best-effort: pushes and pops generally complete in something close to real-time order, but operations happening at nearly the same time on different threads may be reordered slightly. Don't build anything that needs strict global ordering on top of this queue; rely only on the per-pair FIFO guarantee.

Typedef Documentation

◆ PrQueue

typedef struct PrQueue PrQueue

Lock-free pointer FIFO queue

Access it only through the prq* functions - there is no supported direct field access.

Enumeration Type Documentation

◆ PrqGrowthEnum

How much a dynamic PrQueue grows or shrinks by when it resizes.

Enumerator
PRQ_Grow_None 

Do not grow/shrink at all.

PRQ_Grow_25 

Resize by 25%.

PRQ_Grow_50 

Resize by 50%.

PRQ_Grow_100 

Resize by 100% (default)

PRQ_Grow_150 

Resize by 150%.

PRQ_Grow_200 

Resize by 200%.

Definition at line 80 of file prqueue.h.

Function Documentation

◆ prqCollect()

bool prqCollect ( PrQueue prq)

Run one garbage collection cycle on the queue

Reclaims buffer segments that were retired by a previous growth event. Never blocks: if the internal GC lock is already held by another thread, this returns immediately without doing anything. Call it opportunistically, such as from a consumer thread that is about to go idle.

Not needed for correctness, and a no-op on a fixed queue, which never retires segments.

Parameters
prqQueue to run a GC cycle on
Returns
true if the cycle ran, whether or not it collected anything

◆ prqCount()

uint32 prqCount ( PrQueue prq)

Get an estimated count of items in the queue

This is only an estimate, and its accuracy drops the busier the queue is. Use prqPop() returning NULL as the authoritative test for "empty," not a count of zero from this function.

Parameters
prqQueue to inspect
Returns
Approximate number of valid items currently in the queue

◆ prqDestroy()

bool prqDestroy ( PrQueue prq)

Destroy a PrQueue and release its resources

Fails if the queue still holds any entries, since this is a low-level API with no idea what the stored pointers mean or how to clean them up. The caller must pop and dispose of everything, and make sure no thread is still pushing, before calling this.

Parameters
prqQueue to destroy
Returns
true on success, false if entries remain

◆ prqInitDynamic()

void prqInitDynamic ( PrQueue prq,
uint32  minsz,
uint32  targetsz,
uint32  maxsz,
PrqGrowth  growth,
PrqGrowth  shrink 
)

Initialize a growable PrQueue

The queue starts at minsz slots, grows toward targetsz (and up to maxsz) as it fills, and shrinks back down again as load drops. growth and shrink control how large each resize step is.

Always succeeds, or asserts.

Parameters
prqPointer to uninitialized queue structure
minszMinimum and initial size, in pointer slots
targetszSize the queue tries to reach under load
maxszMaximum size it will ever grow to
growthHow much to grow by at a time
shrinkHow much to shrink by at a time

◆ prqInitFixed()

void prqInitFixed ( PrQueue prq,
uint32  sz 
)

Initialize a fixed-size PrQueue

The queue never grows past sz slots; prqPush() fails once it is full. This is the cheaper of the two flavors to operate, and the one to prefer whenever the maximum depth is known ahead of time.

Always succeeds, or asserts.

Parameters
prqPointer to uninitialized queue structure
szFixed capacity, in pointer slots

◆ prqPeek()

void * prqPeek ( PrQueue prq,
uint32  n 
)

Fetch a copy of the nth pointer in the queue without removing it

Warning
This is dangerous. By the time the returned pointer is examined, another thread may already have popped and destroyed whatever it points to, so using it is almost certain to crash unless the caller has external guarantees about which threads pop items and what they do with them, and the pointed-to data is itself thread-safe. Only use this in tightly controlled situations; it is not a general substitute for prqPop().
Parameters
prqQueue to inspect
nIndex of the item to fetch, starting from the head of the queue
Returns
Copy of the nth pointer

◆ prqPop()

void * prqPop ( PrQueue prq)

Pop a pointer from the queue

Parameters
prqQueue to pop from
Returns
The next pointer in the queue, or NULL if the queue is empty

◆ prqPush()

bool prqPush ( PrQueue prq,
void *  ptr 
)

Push a pointer into the queue

ptr must not be NULL. On success, the queue owns the pointer; don't touch it again until it comes back out of a prqPop() call.

Parameters
prqQueue to push into
ptrPointer to push. Must not be NULL
Returns
true on success, false if the queue is full and cannot grow