source: CIVL/examples/experimental/non_blocking_queue_final.cvl@ 7d77e64

main test-branch
Last change on this file since 7d77e64 was ea777aa, checked in by Alex Wilton <awilton@…>, 3 years ago

Moved examples, include, build_default.properties, common.xml, and README out from dev.civl.com into the root of the repo.

git-svn-id: svn://vsl.cis.udel.edu/civl/trunk@5704 fb995dde-84ed-4084-dfe6-e5aef3e2452c

  • Property mode set to 100644
File size: 6.3 KB
Line 
1/* Non-Blocking Concurrent Queue Algorithm
2 * from Michael and Scott
3 * https://www.cs.rochester.edu/research/synchronization/pseudocode/queues.html.
4 * Originally from "Simple, Fast, and
5 * Practical Non-Blocking and Blocking
6 * Concurrent Queue Algorithms", PODC96.
7 *
8 * The free in the algorithm (setFree method
9 * in Dequeue in this code) is meant to
10 * represent a function putting the node back
11 * on to a locally-maintained special-use free
12 * list and not the partner to malloc.
13 * http://blog.shealevy.com/2015/04/23/use-after-free-bug-in-maged-m-michael-and-michael-l-scotts-non-blocking-concurrent-queue-algorithm/#up1
14 */
15#include <civlc.cvh>
16#include <stdio.h>
17#include <stdbool.h>
18#include <stdlib.h>
19#include <assert.h>
20
21typedef struct pointer_t pointer_t;
22typedef struct queue_t queue_t;
23typedef struct node_t node_t;
24typedef struct free_t free_t;
25
26struct pointer_t {
27 node_t* ptr;
28 int count;
29};
30
31struct node_t {
32 int value;
33 pointer_t next;
34};
35
36struct queue_t {
37 pointer_t Head;
38 pointer_t Tail;
39};
40
41/* Structure for linked list consisting of nodes
42 * that we want to free eventually */
43struct free_t {
44 node_t *node;
45 free_t *next;
46};
47
48/* Global list of nodes to be freed */
49free_t* free_list;
50
51void initialize(queue_t *Q) {
52 node_t *node = (node_t*)malloc(sizeof(node_t));
53
54 // why isn't this NULL? (isn't the free_list empty???)
55 free_list=(free_t*)malloc(sizeof(free_t));
56
57 free_list->node = NULL;
58 free_list->next = NULL;
59
60 node->next.ptr = NULL;
61 node->next.count = 0;
62 Q->Head.ptr = Q->Tail.ptr = node;
63}
64
65/* WHAT DOES THIS DO???? Adds the node to the free_list */
66/* free_later ? */
67void setFree(node_t* freeNode) {
68 $atomic {
69 free_t *temp = (free_t*)malloc(sizeof(free_t));
70
71 temp->node = freeNode;
72 temp->next = free_list->next;
73 free_list->next = temp;
74 }
75}
76
77/* what does this do ?
78 free_all(); // deallocates all nodes in free_list
79*/
80void deallocate() {
81 free_t *list = free_list;
82
83 while (list != NULL) {
84 free_t *tmp = list->next;
85
86 free(list->node);
87 free(list);
88 list = tmp;
89 }
90}
91
92/* ??? */
93_Bool ptr_equal(pointer_t p1, pointer_t p2) {
94 return (p1.ptr == p2.ptr) && (p1.count == p2.count);
95}
96
97/* ??? */
98_Bool CAS(pointer_t *dest, pointer_t oldval, pointer_t newval){
99 $atomic {
100 if (ptr_equal(*dest, oldval)) {
101 *dest = newval;
102 return true;
103 }
104 return false;
105 }
106}
107
108void enqueue(queue_t *Q, int value) {
109 pointer_t tail, next;
110 node_t *node = (node_t*)malloc(sizeof(node_t));
111 node->value = value;
112 node->next.ptr = NULL;
113
114 while (true) {
115 tail = Q->Tail;
116 next = tail.ptr->next;
117 if (ptr_equal(tail, Q->Tail)) {
118 if (next.ptr == NULL) {
119 if (CAS(&tail.ptr->next,
120 next,
121 (pointer_t){node, next.count+1}))
122 break;
123 }
124 else {
125 CAS(&Q->Tail,
126 tail,
127 (pointer_t){next.ptr, tail.count+1});
128 }
129 }
130 }
131 CAS(&Q->Tail, tail, (pointer_t){node, tail.count+1});
132}
133
134_Bool dequeue(queue_t *Q, int *pvalue) {
135 pointer_t head, tail, next;
136
137 while (true) {
138 head = Q->Head;
139 tail = Q->Tail;
140 next = head.ptr->next;
141 if (ptr_equal(head, Q->Head)) {
142 if (head.ptr == tail.ptr) {
143 if (next.ptr == NULL)
144 return false;
145 // Tail is falling behind. Try to advance it:
146 CAS(&Q->Tail,
147 tail,
148 (pointer_t){next.ptr, tail.count+1});
149 }
150 else {
151 *pvalue = next.ptr->value;
152 if (CAS(&Q->Head,
153 head,
154 (pointer_t){next.ptr, head.count+1}))
155 break;
156 }
157 }
158 }
159 setFree(head.ptr);
160 return true;
161}
162
163/******************** Tests **************************/
164
165/* Auxiliary function to determine whether an array of
166 * n integers is a permutation of the numbers 0..n-1. */
167_Bool is_permutation(int n, int *data) {
168 _Bool seen[n];
169
170 for (int i=0; i<n; i++)
171 seen[i] = 0;
172 for (int i=0; i<n; i++) {
173 int value = data[i];
174
175 if (value < 0 || value >= n)
176 return 0;
177 if (seen[value])
178 return 0;
179 seen[value] = 1;
180 }
181 return 1;
182}
183
184/* A single thread enqueues 10 items and then dequeues
185 * 10 times. Checks that the dequeued sequence is the
186 * same as the enqueued sequence.
187 */
188void sequentialTest() {
189 int d;
190 queue_t sq;
191
192 initialize(&sq);
193 for (int i = 0; i < 10; i++) {
194 enqueue(&sq, i);
195 }
196 for (int i = 0; i < 10; i++) {
197 _Bool result = dequeue(&sq, &d);
198
199 assert(result);
200 assert(d == i);
201 }
202 deallocate();
203 free(sq.Head.ptr);
204}
205
206/* n threads executing concurrently; each does one enqueue
207 * and one dequeue. Check that the result is a permutation
208 * of the numbers 0..n-1.
209 */
210void permuteTest(int n) {
211 queue_t sq;
212 int array[n];
213
214 initialize(&sq);
215 $parfor (int i: 0 .. (n-1)) {
216 enqueue(&sq, i);
217 dequeue(&sq, &array[i]);
218 }
219 assert(is_permutation(n, array));
220 deallocate();
221 free(sq.Head.ptr);
222}
223
224
225/* Checks that a sequence of integers is obtained
226 * by interleaving blocks of integers.
227 * It is assumed that there are nthreads threads,
228 * each of which generates the integers
229 * nvals*tid, ..., nvals*(tid+1)-1.
230 */
231void assertFIFO(int nthreads, int nvals, int *data) {
232 // for each thread, the next value you expect to see
233 // from that thread:
234 int expect[nthreads];
235
236 for (int tid=0; tid<nthreads; tid++)
237 expect[tid] = tid*nvals;
238 for (int i=0; i< nthreads*nvals; i++) {
239 int x = data[i];
240 int tid = x/nvals;
241
242 assert(expect[tid]==x);
243 expect[tid]++;
244 }
245}
246
247/* Tests the FIFO property where multiple threads enqueue
248 * concurrently and a single thread dequeues everything.
249 * t: number of threads, n: number of values each thread
250 * will enqueue.
251 */
252void test3(int nthreads, int nvals) {
253 // the values dequeued by the single thread:
254 int result[nthreads*nvals];
255 queue_t sq;
256
257 initialize(&sq);
258 $parfor (int tid: 0 .. nthreads-1) {
259 for (int i=0; i<nvals; i++) {
260 enqueue(&sq, i+tid*nvals);
261 }
262 }
263 printf("Dequeued: ");
264 for (int i=0; i<nthreads*nvals; i++) {
265 dequeue(&sq, &result[i]);
266 printf("%d\t", result[i]);
267 }
268 printf("\n");
269 assertFIFO(nthreads, nvals, result);
270 deallocate();
271 free(sq.Head.ptr);
272}
273
274void main() {
275 sequentialTest();
276 permuteTest(3);
277 test3(2, 3);
278}
Note: See TracBrowser for help on using the repository browser.