update
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
|
test_queue
|
||||||
test_queue.*
|
test_queue.*
|
||||||
+103
-25
@@ -3,6 +3,23 @@
|
|||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
#include "lock_free_queue.h"
|
#include "lock_free_queue.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
static int
|
||||||
|
kind_of_atomic_fprintf(FILE *stream, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
char msg[256];
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
int len = vsnprintf(msg, sizeof(msg), fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
if (len < 0) return 0;
|
||||||
|
|
||||||
|
return fwrite(msg, 1, len, stream);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
lock_free_queue_init(struct lock_free_queue *q,
|
lock_free_queue_init(struct lock_free_queue *q,
|
||||||
void *arrptr, int arrlen,
|
void *arrptr, int arrlen,
|
||||||
@@ -12,7 +29,8 @@ lock_free_queue_init(struct lock_free_queue *q,
|
|||||||
|
|
||||||
q->head = 0;
|
q->head = 0;
|
||||||
q->tail = 0;
|
q->tail = 0;
|
||||||
q->temp = 0;
|
q->temp_head = 0;
|
||||||
|
q->temp_tail = 0;
|
||||||
q->size = arrlen / cell;
|
q->size = arrlen / cell;
|
||||||
q->base = arrptr;
|
q->base = arrptr;
|
||||||
q->cell = cell;
|
q->cell = cell;
|
||||||
@@ -21,24 +39,35 @@ lock_free_queue_init(struct lock_free_queue *q,
|
|||||||
static void*
|
static void*
|
||||||
item_addr(struct lock_free_queue *q, int index)
|
item_addr(struct lock_free_queue *q, int index)
|
||||||
{
|
{
|
||||||
assert(index >= 0 && index < q->size);
|
return q->base + q->cell * (index % q->size);
|
||||||
return q->base + q->cell * index;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static uint64_t
|
||||||
acquire_push_location(struct lock_free_queue *q)
|
acquire_push_location(struct lock_free_queue *q)
|
||||||
{
|
{
|
||||||
int old_temp;
|
uint64_t old_temp_tail;
|
||||||
int new_temp;
|
uint64_t new_temp_tail;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
old_temp = q->temp;
|
|
||||||
new_temp = (old_temp + 1) % q->size;
|
uint64_t old_temp_head;
|
||||||
} while (!atomic_compare_exchange_strong(&q->temp, &old_temp, new_temp));
|
|
||||||
return old_temp;
|
old_temp_tail = q->temp_tail;
|
||||||
|
old_temp_head = q->temp_head;
|
||||||
|
|
||||||
|
if (old_temp_head + q->size == old_temp_tail)
|
||||||
|
return UINT64_MAX; // Queue is full
|
||||||
|
|
||||||
|
assert(old_temp_head + q->size > old_temp_tail);
|
||||||
|
|
||||||
|
new_temp_tail = old_temp_tail + 1;
|
||||||
|
} while (!atomic_compare_exchange_weak(&q->temp_tail, &old_temp_tail, new_temp_tail));
|
||||||
|
|
||||||
|
return old_temp_tail;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
release_push_location(struct lock_free_queue *q, int index)
|
release_push_location(struct lock_free_queue *q, uint64_t index)
|
||||||
{
|
{
|
||||||
// The current thread already inserted an
|
// The current thread already inserted an
|
||||||
// item at position "index", which comes
|
// item at position "index", which comes
|
||||||
@@ -51,34 +80,83 @@ release_push_location(struct lock_free_queue *q, int index)
|
|||||||
// Before being able to move the tail over
|
// Before being able to move the tail over
|
||||||
// this element, we need to wait for other
|
// this element, we need to wait for other
|
||||||
// threads to do it.
|
// threads to do it.
|
||||||
|
|
||||||
while (q->tail != index);
|
while (q->tail != index);
|
||||||
|
|
||||||
// At this point only this thread is allowed
|
q->tail++;
|
||||||
// to push the tail forward
|
}
|
||||||
|
|
||||||
q->tail = (q->tail + 1) % q->size;
|
bool
|
||||||
|
lock_free_queue_try_push(struct lock_free_queue *q, void *src)
|
||||||
|
{
|
||||||
|
uint64_t index = acquire_push_location(q);
|
||||||
|
if (index == UINT64_MAX) return false;
|
||||||
|
|
||||||
|
void *dst = item_addr(q, index);
|
||||||
|
memcpy(dst, src, q->cell);
|
||||||
|
|
||||||
|
//kind_of_atomic_fprintf(stdout, "push at %d\n", index % q->size);
|
||||||
|
|
||||||
|
release_push_location(q, index);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
lock_free_queue_push(struct lock_free_queue *q, void *src)
|
lock_free_queue_push(struct lock_free_queue *q, void *src)
|
||||||
{
|
{
|
||||||
int index = acquire_push_location(q);
|
while (!lock_free_queue_try_push(q, src));
|
||||||
|
}
|
||||||
|
|
||||||
void *dst = item_addr(q, index);
|
static uint64_t
|
||||||
|
acquire_pop_location(struct lock_free_queue *q)
|
||||||
|
{
|
||||||
|
uint64_t old_head;
|
||||||
|
uint64_t new_head;
|
||||||
|
do {
|
||||||
|
uint64_t old_tail;
|
||||||
|
|
||||||
|
// It's important to get head before tail
|
||||||
|
// or head may be incremented over tail before
|
||||||
|
// querying it.
|
||||||
|
old_head = q->head;
|
||||||
|
old_tail = q->tail;
|
||||||
|
|
||||||
|
if (old_head == old_tail)
|
||||||
|
return UINT64_MAX;
|
||||||
|
|
||||||
|
assert(old_tail > old_head);
|
||||||
|
|
||||||
|
new_head = old_head + 1;
|
||||||
|
} while (!atomic_compare_exchange_weak(&q->head, &old_head, new_head));
|
||||||
|
return old_head;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
release_pop_location(struct lock_free_queue *q, uint64_t index)
|
||||||
|
{
|
||||||
|
while (q->temp_head != index);
|
||||||
|
|
||||||
|
q->temp_head++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
lock_free_queue_try_pop(struct lock_free_queue *q, void *dst)
|
||||||
|
{
|
||||||
|
uint64_t index = acquire_pop_location(q);
|
||||||
|
if (index == UINT64_MAX)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
void *src = item_addr(q, index);
|
||||||
memcpy(dst, src, q->cell);
|
memcpy(dst, src, q->cell);
|
||||||
|
|
||||||
release_push_location(q, index);
|
//kind_of_atomic_fprintf(stdout, "pop at %d\n", index % q->size);
|
||||||
|
|
||||||
|
release_pop_location(q, index);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
lock_free_queue_pop(struct lock_free_queue *q, void *dst)
|
lock_free_queue_pop(struct lock_free_queue *q, void *dst)
|
||||||
{
|
{
|
||||||
int old_head;
|
while (!lock_free_queue_try_pop(q, dst));
|
||||||
int new_head;
|
|
||||||
do {
|
|
||||||
old_head = q->head;
|
|
||||||
new_head = (old_head + 1) % q->size;
|
|
||||||
void *src = item_addr(q, old_head);
|
|
||||||
memcpy(dst, src, q->cell);
|
|
||||||
} while (!atomic_compare_exchange_strong(&q->head, &old_head, new_head));
|
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-5
@@ -1,19 +1,33 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* +--------------+-----------------+----------------+-------------------+-------------+
|
||||||
|
* | unused | Pop in progress | Items | Push in progress | Unused |
|
||||||
|
* +--------------+-----------------+----------------+-------------------+-------------+
|
||||||
|
* ^ ^ ^ ^
|
||||||
|
* temp_head head tail temp_tail
|
||||||
|
*/
|
||||||
|
|
||||||
struct lock_free_queue {
|
struct lock_free_queue {
|
||||||
|
|
||||||
_Atomic(int) head;
|
_Atomic(uint64_t) head;
|
||||||
_Atomic(int) tail;
|
_Atomic(uint64_t) tail;
|
||||||
_Atomic(int) temp;
|
_Atomic(uint64_t) temp_head;
|
||||||
|
_Atomic(uint64_t) temp_tail;
|
||||||
|
|
||||||
// The following aren't written to by
|
// The following aren't written to by
|
||||||
// push and pop operations, therefore
|
// push and pop operations, therefore
|
||||||
// don't need to be atomic.
|
// don't need to be atomic.
|
||||||
int size; // Capacity of the queue (item count)
|
uint64_t size; // Capacity of the queue (item count)
|
||||||
int cell; // Size of a single item (in bytes)
|
uint64_t cell; // Size of a single item (in bytes)
|
||||||
char *base; // Pointer to the first item
|
char *base; // Pointer to the first item
|
||||||
};
|
};
|
||||||
|
|
||||||
void lock_free_queue_init(struct lock_free_queue *q, void *arrptr, int arrlen, int item_size);
|
void lock_free_queue_init(struct lock_free_queue *q, void *arrptr, int arrlen, int item_size);
|
||||||
void lock_free_queue_push(struct lock_free_queue *q, void *src);
|
void lock_free_queue_push(struct lock_free_queue *q, void *src);
|
||||||
void lock_free_queue_pop(struct lock_free_queue *q, void *dst);
|
void lock_free_queue_pop(struct lock_free_queue *q, void *dst);
|
||||||
|
bool lock_free_queue_try_push(struct lock_free_queue *q, void *src);
|
||||||
|
bool lock_free_queue_try_pop(struct lock_free_queue *q, void *dst);
|
||||||
|
|
||||||
#define lock_free_queue_INIT(q, arr) lock_free_queue_init(q, arr, sizeof(arr), sizeof((arr)[0]))
|
#define lock_free_queue_INIT(q, arr) lock_free_queue_init(q, arr, sizeof(arr), sizeof((arr)[0]))
|
||||||
+3
-25
@@ -2,15 +2,13 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include "tinycthread.h"
|
#include "tinycthread.h"
|
||||||
#include "tinycsem.h"
|
|
||||||
#include "lock_free_queue.h"
|
#include "lock_free_queue.h"
|
||||||
|
|
||||||
#define NUM_THREADS 30
|
#define NUM_THREADS 30
|
||||||
|
|
||||||
struct lock_free_queue queue;
|
#define OPERATIONS_PER_WORKER 100
|
||||||
|
|
||||||
struct tinycsem consumed;
|
struct lock_free_queue queue;
|
||||||
struct tinycsem produced;
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
kind_of_atomic_fprintf(FILE *stream, const char *fmt, ...)
|
kind_of_atomic_fprintf(FILE *stream, const char *fmt, ...)
|
||||||
@@ -27,8 +25,6 @@ kind_of_atomic_fprintf(FILE *stream, const char *fmt, ...)
|
|||||||
return fwrite(msg, 1, len, stream);
|
return fwrite(msg, 1, len, stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define OPERATIONS_PER_WORKER 10000
|
|
||||||
|
|
||||||
int consumer(void *arg)
|
int consumer(void *arg)
|
||||||
{
|
{
|
||||||
(void) arg;
|
(void) arg;
|
||||||
@@ -37,19 +33,12 @@ int consumer(void *arg)
|
|||||||
|
|
||||||
for (int i = 0; i < OPERATIONS_PER_WORKER; i++) {
|
for (int i = 0; i < OPERATIONS_PER_WORKER; i++) {
|
||||||
|
|
||||||
tinycsem_wait(&produced);
|
|
||||||
|
|
||||||
int value;
|
int value;
|
||||||
lock_free_queue_pop(&queue, &value);
|
lock_free_queue_pop(&queue, &value);
|
||||||
|
|
||||||
sum += value;
|
sum += value;
|
||||||
|
|
||||||
kind_of_atomic_fprintf(stderr, "Thread consumed %d (i=%d)\n", value, i);
|
|
||||||
|
|
||||||
tinycsem_signal(&consumed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kind_of_atomic_fprintf(stderr, "Consumer stopped (sum=%d)\n", sum);
|
|
||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,33 +51,24 @@ int producer(void *arg)
|
|||||||
int sum = 0;
|
int sum = 0;
|
||||||
|
|
||||||
for (int i = 0; i < OPERATIONS_PER_WORKER; i++) {
|
for (int i = 0; i < OPERATIONS_PER_WORKER; i++) {
|
||||||
tinycsem_wait(&consumed);
|
|
||||||
|
|
||||||
int value = rand() % 10000;
|
int value = rand() % 10000;
|
||||||
|
|
||||||
lock_free_queue_push(&queue, &value);
|
lock_free_queue_push(&queue, &value);
|
||||||
|
|
||||||
kind_of_atomic_fprintf(stderr, "Thread produced %d (i=%d)\n", value, i);
|
|
||||||
|
|
||||||
sum += value;
|
sum += value;
|
||||||
|
|
||||||
tinycsem_signal(&produced);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kind_of_atomic_fprintf(stderr, "Producer stopped (sum=%d)\n", sum);
|
|
||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define MAX_ITEMS 100
|
#define MAX_ITEMS 10
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
int items[MAX_ITEMS];
|
int items[MAX_ITEMS];
|
||||||
lock_free_queue_INIT(&queue, items);
|
lock_free_queue_INIT(&queue, items);
|
||||||
|
|
||||||
tinycsem_init(&consumed, MAX_ITEMS, MAX_ITEMS);
|
|
||||||
tinycsem_init(&produced, 0, MAX_ITEMS);
|
|
||||||
|
|
||||||
thrd_t threads[NUM_THREADS];
|
thrd_t threads[NUM_THREADS];
|
||||||
int created = 0;
|
int created = 0;
|
||||||
for (int i = 0; i < NUM_THREADS; i++) {
|
for (int i = 0; i < NUM_THREADS; i++) {
|
||||||
@@ -122,7 +102,5 @@ int main()
|
|||||||
produced_sum, consumed_sum,
|
produced_sum, consumed_sum,
|
||||||
(produced_sum == consumed_sum) ? "OK" : "Don't match!!");
|
(produced_sum == consumed_sum) ? "OK" : "Don't match!!");
|
||||||
|
|
||||||
tinycsem_free(&consumed);
|
|
||||||
tinycsem_free(&produced);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
|
|
||||||
all:
|
all:
|
||||||
gcc lock_free_queue_test.c lock_free_queue.c tinycsem.c tinycthread.c -o test_queue -Wall -Wextra
|
gcc lock_free_queue_test.c lock_free_queue.c tinycthread.c -o test_queue -ggdb -Wall -Wextra #-fsanitize=thread
|
||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
#include "tinycsem.h"
|
|
||||||
|
|
||||||
bool tinycsem_init(struct tinycsem *sem, int count, int max)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
SECURITY_ATTRIBUTES *attr = NULL; // Default
|
|
||||||
const char *name = NULL; // No name
|
|
||||||
HANDLE handle = CreateSemaphore(attr, count, max, name);
|
|
||||||
if (handle == NULL)
|
|
||||||
return false;
|
|
||||||
sem->data = handle;
|
|
||||||
return true;
|
|
||||||
#else
|
|
||||||
(void) max; // POSIX doesn't use this
|
|
||||||
return sem_init(&sem->data, 0, count) == 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tinycsem_free(struct tinycsem *sem)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
CloseHandle(sem->data);
|
|
||||||
return true;
|
|
||||||
#else
|
|
||||||
return sem_destroy(&sem->data) == 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tinycsem_wait(struct tinycsem *sem)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
return WaitForSingleObject(sem->data, INFINITE) == WAIT_OBJECT_0;
|
|
||||||
#else
|
|
||||||
return sem_wait(&sem->data) == 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tinycsem_signal(struct tinycsem *sem)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
return ReleaseSemaphore(sem->data, 1, NULL);
|
|
||||||
#else
|
|
||||||
return sem_post(&sem->data) == 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
-47
@@ -1,47 +0,0 @@
|
|||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#else
|
|
||||||
#include <semaphore.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Semaphore structure
|
|
||||||
*/
|
|
||||||
struct tinycsem {
|
|
||||||
#ifdef _WIN32
|
|
||||||
HANDLE data;
|
|
||||||
#else
|
|
||||||
sem_t data;
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Initialize a semaphore with a value of "count".
|
|
||||||
*
|
|
||||||
* The count of the semaphore must always be lower
|
|
||||||
* than "max", else behaviour is undefined.
|
|
||||||
*/
|
|
||||||
bool tinycsem_init(struct tinycsem *sem, int count, int max);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Releases the resources held by a semaphore previously
|
|
||||||
* initialized with "tinycsem_init".
|
|
||||||
*
|
|
||||||
* After this function is used on a semaphore, you can't
|
|
||||||
* reuse it unless you initialize it again.
|
|
||||||
*/
|
|
||||||
bool tinycsem_free(struct tinycsem *sem);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The usual semaphore wait operation.
|
|
||||||
*/
|
|
||||||
bool tinycsem_wait(struct tinycsem *sem);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The usual semaphore signal operation.
|
|
||||||
*/
|
|
||||||
bool tinycsem_signal(struct tinycsem *sem);
|
|
||||||
Reference in New Issue
Block a user