CX Framework
Cross-platform C utility framework
Loading...
Searching...
No Matches
prqueue.h
Go to the documentation of this file.
1
66
67#pragma once
68
69#include <cx/cx.h>
70#include <cx/thread/aspin.h>
71#include <cx/thread/atomic.h>
72#include <cx/thread/mutex.h>
73
74#if DEBUG_LEVEL >= 2 && _64BIT
75#define PRQ_PERF_STATS
76#endif
77CX_C_BEGIN
78
88
89typedef struct PrqSegment PrqSegment;
90
91#ifdef PRQ_PERF_STATS
92typedef struct PrqPerfStats {
93 atomic(uint64) grow;
94 atomic(uint64) grow_collision;
95 atomic(uint64) shrink;
96 atomic(uint64) shrink_collision;
97 atomic(uint64) head_contention;
98 atomic(uint64) reserved_contention;
99 atomic(uint64) push;
100 atomic(uint64) push_optimal;
101 atomic(uint64) push_fast;
102 atomic(uint64) push_slow;
103 atomic(uint64) push_appeared_full;
104 atomic(uint64) push_actually_full;
105 atomic(uint64) push_collision;
106 atomic(uint64) push_retry;
107 atomic(uint64) push_full_retry;
108 atomic(uint64) push_noreserve_retiring;
109 atomic(uint64) pop;
110 atomic(uint64) pop_optimal;
111 atomic(uint64) pop_fast;
112 atomic(uint64) pop_slow;
113 atomic(uint64) pop_nonobvious_empty;
114 atomic(uint64) pop_assist;
115 atomic(uint64) pop_assist_fail;
116 atomic(uint64) pop_segtraverse;
117 atomic(uint64) pop_collision;
118 atomic(uint64) gc_run;
119 atomic(uint64) seg_retired;
120 atomic(uint64) seg_dealloc;
121 atomic(uint64) seg_dealloc_failinuse;
122} PrqPerfStats;
123#endif
124
129typedef struct PrQueue {
130 // Initial size of the queue as well as minimum size.
131 uint32 minsz;
132
133 // Ideal size of the queue.
134 uint32 targetsz;
135
136 // Maximum size of the queue.
137 uint32 maxsz;
138
139 // How much to grow the queue at a time.
140 PrqGrowth growth;
141
142 // How much to shrink the queue at a time.
143 PrqGrowth shrink;
144
145 // Concurrence factor. Defaults to the number of logcal CPUs in the system. This value is
146 // used to determine how many queue entries there must be before threads start assisting
147 // each other to complete operations.
148 uint32 concurrence;
149
150 // Buffer segment that is currently the target for queue pushes and pops. The head of a linked
151 // list of buffer segments when the queue is being grown.
152 atomic(ptr) current;
153
154 // Linked list of buffer segments that have been retired. These are segments that have been
155 // fully emptied and no longer have any valid queue entries, nor can they have any entries
156 // pushed into them, but are being held to deallocate later until it can be guaranteed that
157 // no threads attempting a pop operation still have a pointer to the segment.
158 PrqSegment* retired;
159
160 // Access counter. This is used internally to close a small gap between when a thread retrieves
161 // the current segment pointers and when it increments the use counter, since it cannot do that
162 // atomically while another thread is retiring the segment.
163 atomic(int32) access;
164
165 // Lower 32 bits of timestamp of the last time a segment was added to grow or shrink the queue,
166 // because this needs to be atomic and 64-bit atomics don't exist on all platforms.
167 atomic(uint32) chgtime;
168
169 // Minimum number of milliseconds the queue must wait to shrink after growing or shrinking
170 // (default 500ms).
171 uint32 shrinkms;
172
173 // Running average to track the total queue size across GC cycles for possible shrinking.
174 uint32 avgcount;
175 uint32 avgcount_num;
176
177 // Only 1 thread may run the garbage collection operation at a time. It's recommended to try to
178 // run GC optimistcally when a thread has nothing else to do. For example, a consumer thread
179 // that is about to sleep.
180 Mutex gcmtx;
181
182#ifdef PRQ_PERF_STATS
183 // Performance stats for debugging
184 PrqPerfStats stats;
185#endif
187
188typedef struct PrqSegment {
189 // Next segment in the chain. When the queue grows, it allocates a larger segment which is
190 // temporarily chained to from the original segment in order to handle the transition while
191 // many other threads may be still using the original.
192 atomic(ptr) nextseg;
193
194 // Next retired segment in retired chain.
195 // NOTE: We cannot reuse nextseg for this. nextseg needs to continue to point to the actual
196 // segment that replaced this one, so that any threads which grabbed a pointer to this segment
197 // before it was retired can still follow it.
198 PrqSegment* nextretired;
199
200 // Atomic counter of how many threads are actively using this segment. This, along with the
201 // access counter, act as a non-blocking optimistic lock similar to a reader-writer lock but
202 // less intrusive. They block garbage collection from deallocating this segment if there is
203 // a chance that a thread may still be reading from it (or about to read from it).
204 atomic(int32) inuse;
205
206 // Total number of queue slots in this buffer.
207 uint32 size;
208
209 // Number of queue slots in this buffer that are used. This number may be slightly higher
210 // than the actual number of slots that has been written to. This is the authoritative
211 // source for how much of the queue is used.
212 atomic(uint32) count;
213
214 // Head of the queue; points at the slot that is first in line to be read. This is cached
215 // information for performance optimization only and is not authoritative.
216 atomic(uint32) head;
217
218 // Number of write reservations on this segment. Only used when the buffer is expandable, to
219 // prevent GC from retiring the segment while there are pending write operations. The high
220 // bit is used to signal that the segment is transitioning to the retired status and further
221 // writes may not be started.
222 atomic(uint32) reserved;
223
224 // The actual ringbuffer
225 atomic(ptr) buffer[];
226} PrqSegment;
227
237void prqInitFixed(_Out_ PrQueue* prq, uint32 sz);
238
252void prqInitDynamic(_Out_ PrQueue* prq, uint32 minsz, uint32 targetsz, uint32 maxsz,
253 PrqGrowth growth, PrqGrowth shrink);
254
263_Success_(return) bool prqDestroy(_Pre_valid_ _Post_invalid_ PrQueue* prq);
264
272_Success_(return) bool prqPush(_Inout_ PrQueue* prq, _Pre_notnull_ _Post_invalid_ void* ptr);
273
278_Must_inspect_result_ _Ret_maybenull_ void* prqPop(_Inout_ PrQueue* prq);
279
291bool prqCollect(_Inout_ PrQueue* prq);
292
300uint32 prqCount(_In_ PrQueue* prq);
301
313void* prqPeek(_In_ PrQueue* prq, uint32 n);
314
315CX_C_END
316
318// end of thread_prqueue group
Atomic operations.
bool prqDestroy(PrQueue *prq)
bool prqPush(PrQueue *prq, void *ptr)
PrqGrowthEnum
How much a dynamic PrQueue grows or shrinks by when it resizes.
Definition prqueue.h:80
void prqInitDynamic(PrQueue *prq, uint32 minsz, uint32 targetsz, uint32 maxsz, PrqGrowth growth, PrqGrowth shrink)
uint32 prqCount(PrQueue *prq)
void * prqPeek(PrQueue *prq, uint32 n)
bool prqCollect(PrQueue *prq)
void * prqPop(PrQueue *prq)
void prqInitFixed(PrQueue *prq, uint32 sz)
enum PrqGrowthEnum PrqGrowth
How much a dynamic PrQueue grows or shrinks by when it resizes.
@ PRQ_Grow_100
Resize by 100% (default)
Definition prqueue.h:84
@ PRQ_Grow_150
Resize by 150%.
Definition prqueue.h:85
@ PRQ_Grow_200
Resize by 200%.
Definition prqueue.h:86
@ PRQ_Grow_25
Resize by 25%.
Definition prqueue.h:82
@ PRQ_Grow_None
Do not grow/shrink at all.
Definition prqueue.h:81
@ PRQ_Grow_50
Resize by 50%.
Definition prqueue.h:83
Mutex synchronization primitive.
Definition mutex.h:60