S
Steven Carpenter
I wrote this implementation of a List Queue in C that attempts to be thread-safe by the use of pthread mutexes. The Queue entries hold void pointers to data so the Queue can be generic in type. I would like to improve the code if I can. Please, if anyone has any recommendations on improvements or can see any areas where I've done something wrong I would appreciate your help.
Note, one area that I know needs improvement is the q_delete() method. Thepthread_mutex_destroy() method specifically says not to call it while a mutex is being held, however I know by releasing the mutex it will potentially be given to the next thread that is waiting for it. Is there some type ofatomic way to unlock & destroy or a better design that anyone knows of?
------------------------"q.h"-below-----------------------------
#include <pthread.h>
#include <stdbool.h>
/* Q entry */
typedef struct q_entry_st {
void * data;
struct q_entry_st *next;
} q_entry_t;
/* Q structure */
typedef struct q_st {
struct q_entry_st *back;
struct q_entry_st *front;
int size; /* current size */
pthread_mutex_t mutex;
} q_t;
/* Queue Methods */
q_t *q_new();
void q_delete(q_t *q,
void (*delete_entry)(q_entry_t *entry));
void q_enqueue(q_t *q, q_entry_t *entry);
q_entry_t *q_dequeue(q_t *q);
q_entry_t *q_peek(q_t *q);
bool q_is_empty(q_t *q);
void q_print(q_t *q, void (*print_entry)(q_entry_t *entry));
--------------------------"q.c"-----------------------------------
#include "q.h"
#include <stdlib.h>
#include <pthread.h>
#include <stdbool.h>
q_t *q_new()
{
q_t *q;
/* Allocate queue */
q = (q_t*) calloc(1, sizeof(q_t));
if (!q) return NULL;
/* Initialize pthread mutex */
pthread_mutex_init(&q->mutex, NULL);
return q;
}
void q_delete(q_t *q,
void (*delete_entry)(q_entry_t *entry))
{
if (!q || !delete_entry) return;
pthread_mutex_lock(&q->mutex);
while(!q_is_empty(q)) {
/* Delete all entries */
(*delete_entry)(q_dequeue(q));
}
pthread_mutex_unlock(&q->mutex);
/* Destroy pthread mutex
* Potential race conditions here... */
pthread_mutex_destroy(&q->mutex);
/* Free queue */
free(q);
}
void q_enqueue(q_t *q, q_entry_t *entry)
{
if (!q || !entry) return;
pthread_mutex_lock(&q->mutex);
/* First entry case */
if (q_is_empty(q)) {
q->front = q->back = entry;
}
/* Every other entry case */
else {
q->back->next = entry;
q->back = entry;
}
entry->next = NULL;
q->size++;
pthread_mutex_unlock(&q->mutex);
}
q_entry_t *q_dequeue(q_t *q)
{
if (!q) return NULL;
q_entry_t *entry = NULL;
pthread_mutex_lock(&q->mutex);
if (!q_is_empty(q)) {
/* Grab entry */
entry = q->front;
/* Only element? */
if (q->size == 1) {
q->front = q->back = NULL;
}
else {
q->front = entry->next;
}
q->size--;
}
pthread_mutex_unlock(&q->mutex);
return entry;
}
q_entry_t *q_peek(q_t *q)
{
if (!q) return NULL;
q_entry_t *entry = NULL;
pthread_mutex_lock(&q->mutex);
if (!q_is_empty(q)) {
entry = q->front;
}
pthread_mutex_unlock(&q->mutex);
return entry;
}
bool q_is_empty(q_t *q)
{
if (!q) return true;
pthread_mutex_lock(&q->mutex);
bool empty;
if (q->size == 0) {
empty = true;
} else {
empty = false;
}
pthread_mutex_unlock(&q->mutex);
return empty;
}
/* Prints list front-to-back order */
void q_print(q_t *q,
void (*print_entry)(q_entry_t *entry))
{
if (!q || !print_entry) return;
q_entry_t *entry;
pthread_mutex_lock(&q->mutex);
entry = q->front;
while (entry) {
/* print */
(*print_entry)(entry);
entry = entry->next;
}
}
-------------------------"main.c"-below-------------------
#include <stdio.h>
#include <stdlib.h>
#include "q.h"
void print_int(q_entry_t *entry);
void print_int(q_entry_t *entry)
{
if (!entry) {
printf("Error printing\n");
}
int *x = (int) entry->data;
printf ("Number: %d\n", *x);
}
/*
*
*/
int main(int argc, char** argv) {
q_t *q = q_new();
q_entry_t *a, *b, *c;
int *ai, *bi, *ci;
a = (q_entry_t*) calloc(1, sizeof(q_entry_t));
ai = (int *) calloc(1, sizeof(int));
b = (q_entry_t*) calloc(1, sizeof(q_entry_t));
bi = (int *) calloc(1, sizeof(int));
c = (q_entry_t*) calloc(1, sizeof(q_entry_t));
ci = (int *) calloc(1, sizeof(int));
*ai = 1;
a->data = ai;
*bi = 2;
b->data = bi;
*ci = 3;
c->data = ci;
q_enqueue(q, a);
q_print(q, &print_int);
printf("\n");
q_enqueue(q, b);
q_print(q, &print_int);
printf("\n");
q_enqueue(q, c);
q_print(q, &print_int);
printf("\n");
return 0;
}
Note, one area that I know needs improvement is the q_delete() method. Thepthread_mutex_destroy() method specifically says not to call it while a mutex is being held, however I know by releasing the mutex it will potentially be given to the next thread that is waiting for it. Is there some type ofatomic way to unlock & destroy or a better design that anyone knows of?
------------------------"q.h"-below-----------------------------
#include <pthread.h>
#include <stdbool.h>
/* Q entry */
typedef struct q_entry_st {
void * data;
struct q_entry_st *next;
} q_entry_t;
/* Q structure */
typedef struct q_st {
struct q_entry_st *back;
struct q_entry_st *front;
int size; /* current size */
pthread_mutex_t mutex;
} q_t;
/* Queue Methods */
q_t *q_new();
void q_delete(q_t *q,
void (*delete_entry)(q_entry_t *entry));
void q_enqueue(q_t *q, q_entry_t *entry);
q_entry_t *q_dequeue(q_t *q);
q_entry_t *q_peek(q_t *q);
bool q_is_empty(q_t *q);
void q_print(q_t *q, void (*print_entry)(q_entry_t *entry));
--------------------------"q.c"-----------------------------------
#include "q.h"
#include <stdlib.h>
#include <pthread.h>
#include <stdbool.h>
q_t *q_new()
{
q_t *q;
/* Allocate queue */
q = (q_t*) calloc(1, sizeof(q_t));
if (!q) return NULL;
/* Initialize pthread mutex */
pthread_mutex_init(&q->mutex, NULL);
return q;
}
void q_delete(q_t *q,
void (*delete_entry)(q_entry_t *entry))
{
if (!q || !delete_entry) return;
pthread_mutex_lock(&q->mutex);
while(!q_is_empty(q)) {
/* Delete all entries */
(*delete_entry)(q_dequeue(q));
}
pthread_mutex_unlock(&q->mutex);
/* Destroy pthread mutex
* Potential race conditions here... */
pthread_mutex_destroy(&q->mutex);
/* Free queue */
free(q);
}
void q_enqueue(q_t *q, q_entry_t *entry)
{
if (!q || !entry) return;
pthread_mutex_lock(&q->mutex);
/* First entry case */
if (q_is_empty(q)) {
q->front = q->back = entry;
}
/* Every other entry case */
else {
q->back->next = entry;
q->back = entry;
}
entry->next = NULL;
q->size++;
pthread_mutex_unlock(&q->mutex);
}
q_entry_t *q_dequeue(q_t *q)
{
if (!q) return NULL;
q_entry_t *entry = NULL;
pthread_mutex_lock(&q->mutex);
if (!q_is_empty(q)) {
/* Grab entry */
entry = q->front;
/* Only element? */
if (q->size == 1) {
q->front = q->back = NULL;
}
else {
q->front = entry->next;
}
q->size--;
}
pthread_mutex_unlock(&q->mutex);
return entry;
}
q_entry_t *q_peek(q_t *q)
{
if (!q) return NULL;
q_entry_t *entry = NULL;
pthread_mutex_lock(&q->mutex);
if (!q_is_empty(q)) {
entry = q->front;
}
pthread_mutex_unlock(&q->mutex);
return entry;
}
bool q_is_empty(q_t *q)
{
if (!q) return true;
pthread_mutex_lock(&q->mutex);
bool empty;
if (q->size == 0) {
empty = true;
} else {
empty = false;
}
pthread_mutex_unlock(&q->mutex);
return empty;
}
/* Prints list front-to-back order */
void q_print(q_t *q,
void (*print_entry)(q_entry_t *entry))
{
if (!q || !print_entry) return;
q_entry_t *entry;
pthread_mutex_lock(&q->mutex);
entry = q->front;
while (entry) {
/* print */
(*print_entry)(entry);
entry = entry->next;
}
}
-------------------------"main.c"-below-------------------
#include <stdio.h>
#include <stdlib.h>
#include "q.h"
void print_int(q_entry_t *entry);
void print_int(q_entry_t *entry)
{
if (!entry) {
printf("Error printing\n");
}
int *x = (int) entry->data;
printf ("Number: %d\n", *x);
}
/*
*
*/
int main(int argc, char** argv) {
q_t *q = q_new();
q_entry_t *a, *b, *c;
int *ai, *bi, *ci;
a = (q_entry_t*) calloc(1, sizeof(q_entry_t));
ai = (int *) calloc(1, sizeof(int));
b = (q_entry_t*) calloc(1, sizeof(q_entry_t));
bi = (int *) calloc(1, sizeof(int));
c = (q_entry_t*) calloc(1, sizeof(q_entry_t));
ci = (int *) calloc(1, sizeof(int));
*ai = 1;
a->data = ai;
*bi = 2;
b->data = bi;
*ci = 3;
c->data = ci;
q_enqueue(q, a);
q_print(q, &print_int);
printf("\n");
q_enqueue(q, b);
q_print(q, &print_int);
printf("\n");
q_enqueue(q, c);
q_print(q, &print_int);
printf("\n");
return 0;
}