Add bounded thread_queue implementation
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/time.h>
|
||||
#include "ucore_threadqueue.h"
|
||||
|
||||
|
||||
int uc_thread_queue_init(struct uc_threadqueue *queue, long max_elements)
|
||||
{
|
||||
int rc = 0;
|
||||
if (queue == NULL || max_elements <= 0) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
memset(queue, 0, sizeof(struct uc_threadqueue));
|
||||
|
||||
rc = pthread_cond_init(&queue->read_cond, NULL);
|
||||
if (rc != 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = pthread_cond_init(&queue->write_cond, NULL);
|
||||
if (rc != 0) {
|
||||
pthread_cond_destroy(&queue->read_cond);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = pthread_mutex_init(&queue->mutex, NULL);
|
||||
if (rc != 0) {
|
||||
pthread_cond_destroy(&queue->read_cond);
|
||||
pthread_cond_destroy(&queue->write_cond);
|
||||
return rc;
|
||||
}
|
||||
|
||||
queue->max_elements = max_elements;
|
||||
queue->last = &queue->first;
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int uc_thread_queue_add(struct uc_threadqueue *queue, struct uc_threadmsg *msg)
|
||||
{
|
||||
pthread_mutex_lock(&queue->mutex);
|
||||
|
||||
//Wait if the queue is full
|
||||
while(queue->num_elements >= queue->max_elements) {
|
||||
queue->num_write_waiters++;
|
||||
pthread_cond_wait(&queue->write_cond, &queue->mutex);
|
||||
queue->num_write_waiters--;
|
||||
}
|
||||
|
||||
//insert the new element
|
||||
msg->next = NULL;
|
||||
if(msg->msgtype >= 0) { //add at the tail
|
||||
*(queue->last) = msg;
|
||||
queue->last = &msg->next;
|
||||
} else { //add at the head ("priority message")
|
||||
msg->next = queue->first;
|
||||
if(queue->first == NULL) {
|
||||
queue->last = &msg->next;
|
||||
}
|
||||
queue->first = msg;
|
||||
}
|
||||
|
||||
//signal blocked readers
|
||||
if(queue->num_read_waiters == 1) {
|
||||
//if there's just one thread waiting to pop elements
|
||||
//use _cond_signal. _cond_broadcast can be more expensive (os dependent)
|
||||
pthread_cond_signal(&queue->read_cond);
|
||||
} else if(queue->num_read_waiters > 1) {
|
||||
//signall all threads that there's items available.
|
||||
pthread_cond_broadcast(&queue->read_cond);
|
||||
}
|
||||
|
||||
queue->num_elements++;
|
||||
|
||||
pthread_mutex_unlock(&queue->mutex);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int uc_thread_queue_get(struct uc_threadqueue *queue, const struct timespec *timeout, struct uc_threadmsg **msg)
|
||||
{
|
||||
int rc = 0;
|
||||
struct timespec abstimeout;
|
||||
struct uc_threadmsg *first_msg;
|
||||
|
||||
if (queue == NULL || msg == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
if (timeout) {
|
||||
struct timeval now;
|
||||
|
||||
gettimeofday(&now, NULL);
|
||||
abstimeout.tv_sec = now.tv_sec + timeout->tv_sec;
|
||||
abstimeout.tv_nsec = (now.tv_usec * 1000) + timeout->tv_nsec;
|
||||
if (abstimeout.tv_nsec >= 1000000000) {
|
||||
abstimeout.tv_sec++;
|
||||
abstimeout.tv_nsec -= 1000000000;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&queue->mutex);
|
||||
|
||||
/* Will wait until awakened by a signal or broadcast */
|
||||
while (queue->first == NULL && rc != ETIMEDOUT) {
|
||||
//Need to loop to handle spurious wakeups, or the case
|
||||
//another thread popped the remaining elment before we did
|
||||
queue->num_read_waiters++;
|
||||
if (timeout) {
|
||||
rc = pthread_cond_timedwait(&queue->read_cond, &queue->mutex, &abstimeout);
|
||||
} else {
|
||||
pthread_cond_wait(&queue->read_cond, &queue->mutex);
|
||||
|
||||
}
|
||||
queue->num_read_waiters--;
|
||||
}
|
||||
|
||||
if (rc == ETIMEDOUT) {
|
||||
pthread_mutex_unlock(&queue->mutex);
|
||||
return rc;
|
||||
}
|
||||
|
||||
//remove the first element
|
||||
first_msg = queue->first;
|
||||
queue->first = first_msg->next;
|
||||
if(queue->first == NULL)
|
||||
queue->last = &queue->first;
|
||||
first_msg->next = NULL;
|
||||
|
||||
queue->num_elements--;
|
||||
|
||||
//signal blocked writers
|
||||
if(queue->num_write_waiters == 1) {
|
||||
//if there's just one thread waiting to push elements
|
||||
//use _cond_signal. _cond_broadcast can be more expensive (os dependent)
|
||||
pthread_cond_signal(&queue->write_cond);
|
||||
} else if(queue->num_write_waiters > 1) {
|
||||
//signall all threads that there's items available.
|
||||
pthread_cond_broadcast(&queue->write_cond);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&queue->mutex);
|
||||
*msg = first_msg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//maybe caller should supply a callback for cleaning the elements ?
|
||||
int uc_thread_queue_cleanup(struct uc_threadqueue *queue, int freedata)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (queue == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&queue->mutex);
|
||||
|
||||
/* We don't always know if there's threads still using the queue,
|
||||
* e.g. blocking on the mutex.
|
||||
* It's up to the user to ensure consistency when destroying the queue.
|
||||
*
|
||||
* But if we *know* there are some threads still using the queue,
|
||||
* we can't destroy it.
|
||||
*/
|
||||
if(queue->num_read_waiters != 0 || queue->num_write_waiters != 0) {
|
||||
pthread_mutex_unlock(&queue->mutex);
|
||||
return EBUSY;
|
||||
}
|
||||
|
||||
if(freedata) {
|
||||
struct uc_threadmsg *p;
|
||||
struct uc_threadmsg *next;
|
||||
for(p = queue->first; p; p = next) {
|
||||
next = p->next;
|
||||
free(p);
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&queue->mutex);
|
||||
rc = pthread_mutex_destroy(&queue->mutex);
|
||||
pthread_cond_destroy(&queue->read_cond);
|
||||
pthread_cond_destroy(&queue->write_cond);
|
||||
|
||||
return rc;
|
||||
|
||||
}
|
||||
|
||||
long thread_queue_length(struct uc_threadqueue *queue)
|
||||
{
|
||||
long length;
|
||||
// get the length properly
|
||||
pthread_mutex_lock(&queue->mutex);
|
||||
length = queue->num_elements;
|
||||
pthread_mutex_unlock(&queue->mutex);
|
||||
return length;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user