Commit 14038803 authored by Jens Gustedt's avatar Jens Gustedt
Browse files

implement an "atomic" fifo

This is in fact still suboptimal. It uses two CAS operations per call in
the best case on the fast path and by that is not lock free.
parent 4446502e
/* This may look like nonsense, but it really is -*- mode: C -*- */
/* */
/* Except for parts copied from previous work and as explicitly stated below, */
/* the author and copyright holder for this work is */
/* (C) copyright 2012 Jens Gustedt, INRIA, France */
/* */
/* This file is free software; it is part of the P99 project. */
/* You can redistribute it and/or modify it under the terms of the QPL as */
/* given in the file LICENSE. It is distributed without any warranty; */
/* without even the implied warranty of merchantability or fitness for a */
/* particular purpose. */
/* */
#ifndef P99_FIFO_H
#define P99_FIFO_H 1
#include "p99_enum.h"
#include "p99_generic.h"
/* Additions by C11 */
# if __STDC_VERSION__ < 201100L
# include "p99_atomic.h"
# endif
/**
** @addtogroup atomic C11 atomic operations
** @{
**/
#if defined(P99_DECLARE_ATOMIC) || P00_DOXYGEN
# define P99_FIFO(T) P99_PASTE2(p00_fifo_, T)
# define P99_FIFO_DECLARE(T) \
typedef T volatile P99_PASTE2(p00_fifo_base_, T); \
P99_DECLARE_ATOMIC(P99_PASTE2(p00_fifo_base_, T)); \
typedef _Atomic(P99_PASTE2(p00_fifo_base_, T)) P99_PASTE2(p00_fifo_, T)[2]
# define P99_FIFO_INITIALIZER(HEAD, TAIL) { \
[0] = ATOMIC_VAR_INIT(HEAD), \
[1] = ATOMIC_VAR_INIT(TAIL), \
}
/**
** @brief Append element @a EL to an atomic FIFO @a L
** @see P99_FIFO_CLEAR
** @see P99_FIFO_POP
** @see P00_FIFO_EL
**/
P00_DOCUMENT_PERMITTED_ARGUMENT(P99_FIFO_APPEND, 0)
P00_DOCUMENT_PERMITTED_ARGUMENT(P99_FIFO_APPEND, 1)
#define P99_FIFO_APPEND(L, EL) \
do { \
/* first evaluate the macro arguments such that there can't be */ \
/* name conflicts */ \
register const P99_MACRO_VAR(p00_l, (L)); \
register const P99_MACRO_VAR(p00_el, (EL)); \
register const P99_MACRO_VAR(p00_h, &(*p00_l)[0]); \
register const P99_MACRO_VAR(p00_t, &(*p00_l)[1]); \
p00_el->p99_lifo = 0; \
P99_MACRO_VAR(p00_head, atomic_load(p00_h)); \
for (;;) { \
if (p00_head) { \
/* spin lock the whole fifo */ \
if (atomic_compare_exchange_weak(p00_h, &p00_head, 0)) { \
/* make p00_el the last element */ \
atomic_exchange(p00_t, p00_el)->p99_lifo = p00_el; \
/* unlock the fifo */ \
atomic_store(p00_h, p00_head); \
break; \
} \
} else { \
P99_MACRO_VAR(p00_tail, atomic_load(p00_t)); \
if (!p00_tail \
&& atomic_compare_exchange_weak(p00_t, &p00_tail, p00_el)) { \
/* the fifo was empty, our element is inserted, update the head */ \
atomic_store(p00_h, p00_el); \
break; \
} \
/* we were in the middle of an update of another thread */ \
p00_head = atomic_load(p00_h); \
} \
} \
} while (false)
/**
** @brief Pop the front element from an atomic FIFO @a L
**
** This implements a generic interface to an atomic FIFO (First In -
** First Out) data structure. To use it you just have do some
** preparatory declarations and add a @c p99_lifo field to your data
** structure:
**
** @code
** P99_DECLARE_STRUCT(myData);
** P99_POINTER_TYPE(myData);
** P99_FIFO_DECLARE(myData_ptr);
**
** struct myData {
** ...
** myData_ptr p99_lifo;
** ...
** };
** extern P99_FIFO(myData_ptr) head;
** @endcode
**
** Now @c head can be used as the head of a FIFO:
**
** @code
** myData_ptr el = P99_NEW(myData, \/\* your initializer arguments \*\/);
** P99_FIFO_APPEND(&head, el);
** ...
** for (myData_ptr el = P99_FIFO_POP(&head);
** el;
** el = P99_FIFO_POP(&head)) {
** // do something with el and then
** free(el);
** }
** @endcode
**
** @see P99_FIFO_CLEAR
** @see P99_FIFO
** @see P99_FIFO_DECLARE
** @see P99_FIFO_APPEND
**/
P00_DOCUMENT_PERMITTED_ARGUMENT(P99_FIFO_POP, 0)
#define P99_FIFO_POP(L) \
p99_extension \
({ \
/* first evaluate the macro argument such that there can't be */ \
/* a name conflict */ \
register const P99_MACRO_VAR(p00_l, (L)); \
register const P99_MACRO_VAR(p00_h, &(*p00_l)[0]); \
register const P99_MACRO_VAR(p00_t, &(*p00_l)[1]); \
P99_MACRO_VAR(p00_head, atomic_load(p00_h)); \
for (;;) { \
if (p00_head) { \
/* spin lock the whole fifo */ \
if (atomic_compare_exchange_weak(p00_h, &p00_head, 0)) { \
if (p00_head->p99_lifo) \
/* there is still another element to come in the fifo, make it \
the head */ \
atomic_store(p00_h, p00_head->p99_lifo); \
else \
/* this was the last element in the fifo, set the tail to 0, \
too */ \
atomic_store(p00_t, 0); \
p00_head->p99_lifo = 0; \
break; \
} \
} else { \
register P99_MACRO_VAR(p00_tail, atomic_load(p00_t)); \
if (!p00_tail) break; \
p00_head = atomic_load(p00_h); \
} \
} \
/* make sure that the result can not be used as an lvalue */ \
register const __typeof__(p00_head = p00_head) p00_r = p00_head; \
p00_r; \
})
/**
** @brief Atomically clear an atomic FIFO @a L and return a pointer
** to the start of the list that it previously contained
**
** @see P99_FIFO
** @see P99_FIFO_DECLARE
** @see P99_FIFO_APPEND
** @see P00_FIFO_EL
**/
P00_DOCUMENT_PERMITTED_ARGUMENT(P99_FIFO_CLEAR, 0)
#define P99_FIFO_CLEAR(L) \
p99_extension \
({ \
/* first evaluate the macro argument such that there can't be */ \
/* a name conflict */ \
register const P99_MACRO_VAR(p00_l, (L)); \
register const P99_MACRO_VAR(p00_h, &(*p00_l)[0]); \
register const P99_MACRO_VAR(p00_t, &(*p00_l)[1]); \
P99_MACRO_VAR(p00_head, atomic_load(p00_h)); \
for (;;) { \
if (p00_head) { \
/* spin lock the whole fifo */ \
if (atomic_compare_exchange_weak(p00_h, &p00_head, 0)) { \
atomic_store(p00_t, 0); \
break; \
} \
} else { \
register const P99_MACRO_VAR(p00_tail, atomic_load(p00_t)); \
if (!p00_tail) break; \
p00_head = atomic_load(p00_h); \
} \
} \
/* make sure that the result can not be used as an lvalue */ \
register const __typeof__(p00_head = p00_head) p00_r = p00_head; \
p00_r; \
})
#else
/* A fall back implementation for the case that there are no atomic
operations available */
# define P99_FIFO(T) P99_PASTE2(p00_fifo_, T)
# define P99_FIFO_DECLARE(T) typedef T P99_PASTE2(p00_fifo_, T)[2]
# define P99_FIFO_INITIALIZER(HEAD, TAIL) { [0] = (HEAD), [1] = (TAIL) }
#define P99_FIFO_APPEND(L, EL) \
p99_extension \
({ \
P99_MACRO_VAR(p00_l, (L)); \
P99_MACRO_VAR(p00_el, (EL)); \
p00_el->p99_lifo = (*p00_l)[1]; \
(*p00_l)[1] = p00_el; \
if (!(*p00_l)[0]) (*p00_l)[0] = p00_el; \
})
#define P99_FIFO_POP(L) \
p99_extension \
({ \
P99_MACRO_VAR(p00_l, (L)); \
P99_MACRO_VAR(p00_el, (*p00_l)[0]); \
(*p00_l)[0] = p00_el->p99_lifo; \
if (!(*p00_l)[0]) = (*p00_l)[0] = 0; \
if (p00_el) p00_el->p99_lifo = 0; \
/* be sure that the result can not be used as an lvalue */ \
register const __typeof__(p00_el = p00_el) p00_r = p00_el; \
p00_r; \
})
P00_DOCUMENT_PERMITTED_ARGUMENT(P99_FIFO_CLEAR, 0)
#define P99_FIFO_CLEAR(L) \
({ \
P99_MACRO_VAR(p00_l, (L)); \
register P99_MACRO_VAR(p00_ret, (*p00_l)[0]); \
(*p00_l)[0] = 0; \
(*p00_l)[1] = 0; \
p00_ret; \
})
#endif
P00_DOCUMENT_TYPE_ARGUMENT(P99_FIFO_TABULATE, 0)
P00_DOCUMENT_IDENTIFIER_ARGUMENT(P99_FIFO_TABULATE, 1)
P00_DOCUMENT_PERMITTED_ARGUMENT(P99_FIFO_TABULATE, 2)
#define P99_FIFO_TABULATE(TYPE, TAB, L) \
size_t P99_FILEID(TAB, _cnt) = 0; \
TYPE * P99_FILEID(TAB, _head) = P99_FIFO_CLEAR(L); \
for (TYPE * p00_e = P99_FILEID(TAB, _head); \
p00_e; \
p00_e = p00_e->p99_lifo) \
++P99_FILEID(TAB, _cnt); \
TYPE * TAB[P99_FILEID(TAB, _cnt)]; \
for (TYPE ** p00_t = &(TAB[0]), \
* p00_e = P99_FILEID(TAB, _head); \
p00_e; \
p00_e = p00_e->p99_lifo, \
++p00_t) \
*p00_t = p00_e
/**
** @}
**/
#endif
......@@ -21,6 +21,7 @@
#include "p99_count.h"
#include "p99_enum.h"
#include "p99_errno.h"
#include "p99_fifo.h"
#include "p99_generic.h"
#include "p99_int.h"
#include "p99_map.h"
......
......@@ -13,6 +13,7 @@
#include "p99_threads.h"
#include "p99_generic.h"
#include "p99_clib.h"
#include "p99_fifo.h"
void * p00_thrd_create(void* context);
......@@ -24,10 +25,13 @@ static atomic_double D;
static atomic_uint U;
P99_DECLARE_STRUCT(tester);
P99_POINTER_TYPE(tester);
P99_FIFO_DECLARE(tester_ptr);
struct tester {
int a;
double b;
tester* p99_lifo;
};
P99_DECLARE_ATOMIC(tester, atomic_tester);
......@@ -38,9 +42,19 @@ P99_DECLARE_ATOMIC(int*, atomic_intp);
P99_DECLARE_ATOMIC(int*, atomic_intp2);
atomic_intp intp;
P99_FIFO(tester_ptr) ober_tester = P99_FIFO_INITIALIZER(0, 0);
static
void aqe(void) {
fprintf(stderr, "quick exit!\n");
fprintf(stderr, "quick exit!:");
tester_ptr unter_tester = P99_FIFO_CLEAR(&ober_tester);
while (unter_tester) {
tester_ptr next = unter_tester->p99_lifo;
fprintf(stderr, " %d (%p)", unter_tester ? unter_tester->a : INT_MAX, (void*)unter_tester);
free(unter_tester);
unter_tester = next;
}
fputc('\n', stderr);
}
static
......@@ -50,7 +64,9 @@ void ate0(void) {
static
void ate1(void) {
fprintf(stderr, "thread exit 1!\n");
tester_ptr unter_tester = P99_FIFO_POP(&ober_tester);
fprintf(stderr, "thread exit 1! %d (%p)\n", unter_tester ? unter_tester->a : INT_MAX, (void*)unter_tester);
free(unter_tester);
}
static
......@@ -80,6 +96,9 @@ int real_task(atomic_intp* arg) {
case 1: at_quick_exit(aqe); break;
case 2: at_thrd_exit(ate1); break;
}
tester_ptr unter_tester = P99_MALLOC(tester);
*unter_tester = (tester){ .a = ret, };
P99_FIFO_APPEND(&ober_tester, unter_tester);
if (ret % 2)
return -1;
else
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment