This commit is contained in:
2024-03-23 22:53:10 +01:00
parent 8a05101f0d
commit 172462b023
7 changed files with 129 additions and 150 deletions
+1
View File
@@ -1 +1,2 @@
test_queue
test_queue.* test_queue.*
+104 -26
View File
@@ -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
View File
@@ -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]))
+4 -26
View File
@@ -2,17 +2,15 @@
#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
#define OPERATIONS_PER_WORKER 100
struct lock_free_queue queue; struct lock_free_queue queue;
struct tinycsem consumed; static int
struct tinycsem produced;
static int
kind_of_atomic_fprintf(FILE *stream, const char *fmt, ...) kind_of_atomic_fprintf(FILE *stream, const char *fmt, ...)
{ {
char msg[256]; char msg[256];
@@ -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 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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);