Despite the number of similar questions on Stackoverflow, I can't come up with a solution for the following Producer-Consumer problem:
My program has three threads:
One writer thread that reads from a file, saves data into a sensor_data_t struct, and writes it into a dynamic pointer-based buffer using sbuffer_insert(buffer, &sensor_data). Once this thread finishes reading it sends an end-of-stream data struct represented by data->id == 0.
Two reader threads that remove data from the buffer head (FIFO-style), and store it into a temporary data struct using sbuffer_remove(buffer, &data) and then print it to the cmd line for testing purposes.
I think I have to avoid at the least:
My reader threads to try to consume/remove from the buffer while it is empty.
My reader threads to consume/remove from the buffer at the same time.
On the other hand, I don't think my writer thread in sbuffer_insert() needs to worry if the readers are changing the head because it only appends to the tail. Is this reasoning correct or am I missing something?
Here's what I've done so far:
My main function:
sbuffer_t *buffer;
void *writer(void *fp);
void *reader(void *fp);
int main()
{
// Initialize the buffer
sbuffer_init(&buffer);
// Open sensor_data file
FILE *sensor_data_fp;
sensor_data_fp = fopen("sensor_data", "rb");
// Start thread for reading sensor_data file adding elements to the sbuffer
pthread_t writer_thread;
pthread_create(&writer_thread, NULL, &writer, sensor_data_fp);
// Open sensor_data_out file
FILE *sensor_data_out_fp;
sensor_data_out_fp = fopen("sensor_data_out", "w");
// Start thread 1 and 2 for writing sensor_data_out file
pthread_t reader_thread1;
pthread_t reader_thread2;
pthread_create(&reader_thread1, NULL, &reader, sensor_data_out_fp);
pthread_create(&reader_thread2, NULL, &reader, sensor_data_out_fp);
// Wait for threads to finish and join them
pthread_join(reader_thread1, NULL);
pthread_join(reader_thread2, NULL);
pthread_join(writer_thread, NULL);
// Close sensor_data file
fclose(sensor_data_fp);
// Close sensor_data_out file
fclose(sensor_data_out_fp);
// free buffer
sbuffer_free(&buffer);
return 0;
}
My reader and writer threads:
typedef uint16_t sensor_id_t;
typedef double sensor_value_t;
typedef time_t sensor_ts_t; // UTC timestamp as returned by time() - notice that the size of time_t is different on 32/64 bit machine
typedef struct {
sensor_id_t id;
sensor_value_t value;
sensor_ts_t ts;
} sensor_data_t;
void *writer(void *fp)
{
// cast fp to FILE
FILE *sensor_data_fp = (FILE *)fp;
// make char buffers of size sensor_id_t, sensor_value_t and sensor_ts_t
char sensor_id_buffer[sizeof(sensor_id_t)];
char sensor_value_buffer[sizeof(sensor_value_t)];
char sensor_ts_buffer[sizeof(sensor_ts_t)];
// parse sensor_data file into sensor_id_buffer, sensor_value_buffer and sensor_ts_buffer
while(fread(sensor_id_buffer, sizeof(sensor_id_t), 1, sensor_data_fp) == 1 &&
fread(sensor_value_buffer, sizeof(sensor_value_t), 1, sensor_data_fp) == 1 &&
fread(sensor_ts_buffer, sizeof(sensor_ts_t), 1, sensor_data_fp)) {
// create sensor_data_t
sensor_data_t sensor_data;
// copy sensor_id_buffer to sensor_data.id
memcpy(&sensor_data.id, sensor_id_buffer, sizeof(sensor_id_t));
// copy sensor_value_buffer to sensor_data.value
memcpy(&sensor_data.value, sensor_value_buffer, sizeof(sensor_value_t));
// copy sensor_ts_buffer to sensor_data.ts
memcpy(&sensor_data.ts, sensor_ts_buffer, sizeof(sensor_ts_t));
// print sensor_data for testing
// printf("sensor_data.id: %d, sensor_data.value: %f, sensor_data.ts: %ld\n", sensor_data.id, sensor_data.value, sensor_data.ts);
// insert sensor_data into buffer
sbuffer_insert(buffer, &sensor_data);
}
// Add dummy data to buffer to signal end of file
sensor_data_t sensor_data;
sensor_data.id = 0;
sensor_data.value = 0;
sensor_data.ts = 0;
sbuffer_insert(buffer, &sensor_data);
return NULL;
}
void *reader(void *fp)
{
// cast fp to FILE
//FILE *sensor_data_out_fp = (FILE *)fp;
// Init data as sensor_data_t
sensor_data_t data;
do{
// read data from buffer
if (sbuffer_remove(buffer, &data) == 0) { // SBUFFER_SUCCESS 0
// write data to sensor_data_out file
// fwrite(&data, sizeof(sensor_data_t), 1, sensor_data_out_fp);
// print data for testing
printf("data.id: %d, data.value: %f, data.ts: %ld \n", data.id, data.value, data.ts);
}
}
while(data.id != 0);
// free allocated memory
// free(fp);
return NULL;
}
Global variables and buffer initialization:
typedef struct sbuffer_node {
struct sbuffer_node *next;
sensor_data_t data;
} sbuffer_node_t;
struct sbuffer {
sbuffer_node_t *head;
sbuffer_node_t *tail;
};
pthread_mutex_t mutex;
pthread_cond_t empty, removing;
int count = 0; // reader count
int sbuffer_init(sbuffer_t **buffer) {
*buffer = malloc(sizeof(sbuffer_t));
if (*buffer == NULL) return SBUFFER_FAILURE;
(*buffer)->head = NULL;
(*buffer)->tail = NULL;
// Initialize mutex and condition variables
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&empty, NULL);
pthread_cond_init(&removing, NULL);
return SBUFFER_SUCCESS;
}
sbuffer_remove (Consumer)
int sbuffer_remove(sbuffer_t *buffer, sensor_data_t *data) {
sbuffer_node_t *dummy;
if (buffer == NULL) return SBUFFER_FAILURE;
// while the count is 0, wait
pthread_mutex_lock(&mutex);
while (count > 0) {
pthread_cond_wait(&removing, &mutex);
}
pthread_mutex_unlock(&mutex);
pthread_mutex_lock(&mutex);
while (buffer->head == NULL){
pthread_cond_wait(&empty, &mutex); // Wait until buffer is not empty
if (data->id == 0){ // end-of-stream
pthread_mutex_unlock(&mutex);
return SBUFFER_NO_DATA;
}
}
count++;
*data = buffer->head->data;
dummy = buffer->head;
if (buffer->head == buffer->tail) // buffer has only one node
{
buffer->head = buffer->tail = NULL;
} else // buffer has many nodes empty
{
buffer->head = buffer->head->next;
}
free(dummy);
count--;
pthread_cond_signal(&removing); // Signal that data is removed
pthread_mutex_unlock(&mutex);
return SBUFFER_SUCCESS;
}
sbuffer_insert (Producer)
int sbuffer_insert(sbuffer_t *buffer, sensor_data_t *data) {
sbuffer_node_t *dummy;
if (buffer == NULL) return SBUFFER_FAILURE;
dummy = malloc(sizeof(sbuffer_node_t));
if (dummy == NULL) return SBUFFER_FAILURE;
dummy->data = *data;
dummy->next = NULL;
if (buffer->tail == NULL) // buffer empty (buffer->head should also be NULL
{
pthread_mutex_lock(&mutex);
buffer->head = buffer->tail = dummy;
pthread_cond_signal(&empty); // Signal that buffer is not empty
pthread_mutex_unlock(&mutex);
} else // buffer not empty
{
buffer->tail->next = dummy;
buffer->tail = buffer->tail->next;
}
return SBUFFER_SUCCESS;
}
Currently, the code has very unstable behavior. Sometimes it runs and prints everything, sometimes it doesn't print anything and gets stuck in a loop, sometimes it prints everything but the last value comes after the end-of-stream code and it doesn't terminate.
I would really appreciate a solution that explains what I'm doing wrong or a comment that redirects me to a duplicate of my question.
I think I have to avoid at the least:
My reader threads to try to consume/remove from the buffer while it is empty.
My reader threads to consume/remove from the buffer at the same time.
Yes, you must avoid those. And more.
On the other hand, I don't think my writer thread in sbuffer_insert()
needs to worry if the readers are changing the head because it only
appends to the tail. Is this reasoning correct or am I missing
something?
You are missing at least that
when the buffer contains fewer than two nodes, there is no distinction between the head node and the tail node. This manifests at the code level at least in the fact that your sbuffer_insert() and sbuffer_remove() functions both access both buffer->head and buffer->tail. From the perspective of synchronization requirements, it is this lower-level view that matters.
Insertion and removal modifies the node objects themselves, not just the overall buffer object.
Synchronization is not just, nor even primarily, about avoiding threads directly interfering with each other. It is even more about the consistency of different threads' views of memory. You need appropriate synchronization to ensure that one thread's writes to memory are (ever) observed by other threads, and to establish ordering relationships among operations on memory by different threads.
Currently, the code has very unstable behavior. Sometimes it runs and
prints everything, sometimes it doesn't print anything and gets stuck
in a loop, sometimes it prints everything but the last value comes
after the end-of-stream code and it doesn't terminate.
This is unsurprising, because your program contains data races, and its behavior is therefore undefined.
Do ensure that neither the reader nor the writer accesses any member of the buffer object without holding the mutex locked. As the code is presently structured, that will synchronize access not only to the buffer structure itself, but also to the data in the nodes, which presently are involved in their own data races.
Now note that here ...
while (buffer->head == NULL){
pthread_cond_wait(&empty, &mutex); // Wait until buffer is not empty
if (data->id == 0){ // end-of-stream
pthread_mutex_unlock(&mutex);
return SBUFFER_NO_DATA;
}
}
... you are testing for an end-of-data marker before actually reading an item from the buffer. It looks like that's useless in practice. In prarticular, it does not prevent the end-of-stream item from being removed from the buffer, so only one reader will see it. The other(s) will then end up waiting indefinitely for data that will never arrive.
Next, consider this code executed by the readers:
// while the count is 0, wait
pthread_mutex_lock(&mutex);
while (count > 0) {
pthread_cond_wait(&removing, &mutex);
}
pthread_mutex_unlock(&mutex);
Note well that the reader unlocks the mutex while count is 0, so there is an opportunity for another reader to reach the while loop and pass through. I'm not sure that two threads both getting past that point at the same time produces a problem in practice, but the point seems to be to avoid that, so do it right: move the count++ from later in the function to right after the while loop, prior to unlocking the mutex.
Alternatively, once you've done that, it should be clear(er) that you've effectively hand-rolled a binary semaphore. You could simplify your code by switching to an actual POSIX semaphore for this purpose. Or if you want to continue with a mutex + CV for this, then consider using a different mutex, as the data to be protected for this purpose are disjoint from the buffer and its contents. That would get rid of the weirdness of re-locking the mutex immediately after unlocking it.
Or on the third hand, consider whether you need to do any of that at all. How is the (separate) mutex protection of the rest of the body of sbuffer_remove() not sufficient by itself? I propose to you that it is sufficient. After all, you're presently using your hand-rolled semaphore exactly as (another) mutex.
The bones of this code seem reasonably good, so I don't think repairs will be too hard.
First, add the needed mutex protection in sbuffer_insert(). Or really, just expand the scope of the critical section that's already there:
int sbuffer_insert(sbuffer_t *buffer, sensor_data_t *data) {
sbuffer_node_t *dummy;
if (buffer == NULL) return SBUFFER_FAILURE;
dummy = malloc(sizeof(sbuffer_node_t));
if (dummy == NULL) return SBUFFER_FAILURE;
dummy->data = *data;
dummy->next = NULL;
pthread_mutex_lock(&mutex);
if (buffer->tail == NULL) // buffer empty (buffer->head should also be NULL
{
assert(buffer->head == NULL);
buffer->head = buffer->tail = dummy;
pthread_cond_signal(&empty); // Signal that buffer is not empty
} else // buffer not empty
{
buffer->tail->next = dummy;
buffer->tail = buffer->tail->next;
}
pthread_mutex_unlock(&mutex);
return SBUFFER_SUCCESS;
}
Second, simplify and fix sbuffer_remove():
int sbuffer_remove(sbuffer_t *buffer, sensor_data_t *data) {
if (buffer == NULL) {
return SBUFFER_FAILURE;
}
pthread_mutex_lock(&mutex);
// Wait until the buffer is nonempty
while (buffer->head == NULL) {
pthread_cond_wait(&empty, &mutex);
}
// Copy the first item from the buffer
*data = buffer->head->data;
if (data->id == 0) {
// end-of-stream: leave the item in the buffer for other readers to see
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&empty); // other threads can consume this item
return SBUFFER_NO_DATA;
} // else remove the item
sbuffer_node_t *dummy = buffer->head;
buffer->head = buffer->head->next;
if (buffer->head == NULL) {
// buffer is now empty
buffer->tail = NULL;
}
pthread_mutex_unlock(&mutex);
free(dummy);
return SBUFFER_SUCCESS;
}
Not a complete answer here, but I see this in your sbuffer_remove function:
// while the count is 0, wait
pthread_mutex_lock(&mutex);
while (count > 0) {
pthread_cond_wait(&removing, &mutex);
}
pthread_mutex_unlock(&mutex);
That looks suspicious to me. What is the purpose of waiting for the count to become zero? Your code waits for the count to become zero, but then it does nothing else before it unlocks the mutex.
I don't know what count represents, but if the other reader thread is concurrently manipulating it, then there is no guarantee that it will still be zero once you've unlocked the mutex.
But, maybe that hasn't caused a problem for you because...
...This also looks suspicious:
pthread_mutex_unlock(&mutex);
pthread_mutex_lock(&mutex);
Why do you unlock the mutex and immediately lock it again? Are you thinking that will afford the other consumer a chance to lock it? Technically speaking, it does that, but in practical terms, the chance it offers is known as, "a snowball's chance in Hell." If thread A is waiting for a mutex that is locked by thread B, and thread B unlocks and then immediately tries to re-lock, then in most languages/libraries/operating systems, thread B will almost always succeed while thread A goes back to try again.
Mutexes work best when they are rarely contested. If you have a program in which threads spend any significant amount of time waiting for mutexes, then you probably are not getting much benefit from using multiple threads.
Related
I want to use condition variables to launch at most N thread to process all files one one huge directory (1M files).
The code seems to work but after some times, it blocks in main thread. Below the frustrating code:
void* run(void* ctx)
{
clientCtx* client = (clientCtx*)ctx;
printf("New file from thread %d: %s\n", client->num, client->filename);
free(client->filename);
pthread_mutex_lock(&clientFreeMutex);
client->state = IDLE_STATE;
pthread_cond_signal(&clientFreeCond);
printf("Thread %d is free\n", client->num);
pthread_mutex_unlock(&clientFreeMutex);
return NULL;
}
int main(int argc, char** argv)
{
pthread_t client[MAX_CLIENT] = {0};
clientCtx ctx[MAX_CLIENT] = {0};
DIR* directory = NULL;
struct dirent* element = NULL;
/* Initialize condition variable for max clients */
pthread_mutex_init(&clientFreeMutex, NULL);
pthread_cond_init(&clientFreeCond, NULL);
/* Initialize contexts for clients */
for (int cnt = 0; cnt < MAX_CLIENT; cnt ++)
{
ctx[cnt].state = IDLE_STATE;
ctx[cnt].num = cnt;
}
directory = opendir(argv[1]);
while((element = readdir(directory)) != NULL)
{
pthread_mutex_lock(&clientFreeMutex);
int cnt;
for (cnt = 0; cnt < MAX_CLIENT; cnt++)
{
if(ctx[cnt].state == IDLE_STATE)
{
ctx[cnt].filename = strdup(element->d_name);
ctx[cnt].state = BUSY_STATE;
pthread_create(&client[cnt], NULL, run, &(ctx[cnt]));
break;
}
}
/* No free client */
if (cnt == MAX_CLIENT)
{
printf("No free thread. Waiting.\n");
pthread_cond_wait(&clientFreeCond, &clientFreeMutex);
}
pthread_mutex_unlock(&clientFreeMutex);
}
closedir(directory);
exit(EXIT_SUCCESS);
}
What is the problem? thanks for your help :)
Warning you use the value of readdir in separate threads without any protection against the multi-threading, so when you (try to) printf client->file->d_name may be you are doing at the same time readdir in the main thread modifying the saved result, this has an undefined behavior.
You need for example to save a strdup of element->file->d_name in main and save that string in the clientCtx rather than the struct dirent *, and of course to free it in run
Note also a closedir is missing at the end of main even in this case it is not a real problem (just do to remember for your other programs).
I finally found the problem: launched threads were not joined and pthread_create finally returned an error code with errno message set to "Could not allocate memory". The signal was never sent and the main thread was then blocking.
I fixed this creating a new state for already launched threads and adding a join in main loop.
I am using array with 2 threads. One is writing to it and another reading. You can assume that reading is slower than writing. The reader and writer are separate pthreads. As far as I know sharing an array as a global variable between those threads is safe.
So overall picture is like:
char ** array;
void writer(void){
for (unsigned long i = 0; i < maxArraySize; i++){
array[i] = malloc(someSize);
array[i] = someCharArray;
}
void reader(void){
for (unsigned long i = 0; i < maxArraySize; i++){
if(array[i] == NULL){ // in case reader is faster
i--;
continue;
}
useData(array[i]);
free(array[i]); // HERE IS MY QUESTION..
}
main(){
array = malloc(maxArraySize);
pthread_t reader, writer;
pthread_create( &reader, NULL, reader, NULL);
pthread_create( &writer, NULL, writer, NULL);
}
My question is related with line where I free i'th element of array. Is it safe to do it? Because when I free i'th element, at the same time, write is writing to array. So can there be a case that writer gets wrong address as it can lose the head pointer?
No it is not safe if you read during a write without a special instruction the result is undefined. You could get any value, though it is unlikely that you will see any other than NULL or the one you had assigned.
As others have mentioned in the comments the un-initialized array may contain anything (it is undefined) though it is likely zeroed before the kernel gave it to you.
If you want safety you need a locking mechanism such as a semaphore (http://man7.org/linux/man-pages/man3/sem_init.3.html).
char ** array;
// Allows access while non zero
sem_t sem;
void writer(void){
for (unsigned long i = 0; i < maxArraySize; i++){
array[i] = malloc(someSize);
array[i] = someCharArray;
// Increment semaphore.
sem_post(&sem);
}
void reader(void){
for (unsigned long i = 0; i < maxArraySize; i++){
// Will return -1 if the semaphore is not at zero
// Will return 0 if semaphore is greater than zero and decrement it.
if(sem_trywait(&sem)){ // in case reader is faster
i--;
continue;
}
useData(array[i]);
free(array[i]); // HERE IS MY QUESTION..
}
main(){
// Initialize semaphore to zero
sem_init(&sem, 0 , 0);
// Initialize array to have maxArraySize elements.
array = malloc(maxArraySize * sizeof(*array));
pthread_t reader, writer;
pthread_create( &reader, NULL, reader, NULL);
pthread_create( &writer, NULL, writer, NULL);
}
This should be fast but will spin your cpu doing a lot of nothing at the sem_trywait. Use sem_wait if you can wait a little longer and do not need the spinning.
I also corrected the bug in your malloc statement because it was not allocating enough space for maxArraySize char * items.
I saw a blog stating the below code is thread safe , but the condition count not being inside the mutex would cause a data corruption; in case two threads check the count at the same time but before either acquires a mutex and one acquire the mutex after contending. When one thread completes , the other would very blindly add one more value to the array.
char arr[10];
int count=0;
int func(char ctr)
{
int i=0;
if(count >= sizeof(arr))
{
printf("\n No storage\n");
return -1;
}
/* lock the mutex here*/
arr[count] = ctr;
count++;
/* unlock the mutex here*/
return count;
}
Would I be right if I made the following changes? Or is there a better/efficient way to do it
int func(char ctr)
{
int i=0;
/* lock the mutex here*/
if(count >= sizeof(arr))
{
printf("\n No storage\n");
/* unlock the mutex here*/
return -1;
}
arr[count] = ctr;
count++;
/* unlock the mutex here*/
return count;
}`
You are correct. By doing the check outside of the critical section you are opening the doors for a possible buffer overrun. However, note that the returned count may not be the same index used to store ctr. That's an issue even in the corrected code.
In order to remedy that you could rewrite it like this:
int func(char ctr)
{
/* lock the mutex here*/
if(count >= sizeof(arr)) {
printf("\n No storage\n");
/* unlock the mutex here*/
return -1;
}
arr[count] = ctr;
int c = count++;
/* unlock the mutex here*/
return c;
}
It's worth noting that, if that's the only function changing "count", then no two threads would be able to change the same memory position in arr and this would actually be safe:
int func(char ctr)
{
/* lock the mutex here*/
if(count >= sizeof(arr)) {
printf("\n No storage\n");
/* unlock the mutex here*/
return -1;
}
int c = count++;
/* unlock the mutex here*/
arr[c] = ctr;
return c;
}
If that's the pattern, maybe you can refactor that code into two functions, like so:
int get_sequence(void)
{
/* lock the mutex here*/
int c = count++;
/* unlock the mutex here*/
return c;
}
int func(char ctr)
{
int c = get_sequence();
if(c >= sizeof(arr)) {
printf("\n No storage\n");
return -1;
}
arr[c] = ctr;
return c;
}
Note that will only work as long as get_sequence is really the only function changing count variable.
First, you are correct that the code from the blog has the potential to write beyond the end of the array. The limit checking only works if it's done after the mutex has been acquired.
Here's how I would write the function:
bool func(char ctr)
{
bool result;
/* lock the mutex here */
if (count >= sizeof(arr))
{
result = FAILURE;
}
else
{
arr[count] = ctr;
count++;
result = SUCCESS;
}
/* unlock the mutex here */
if ( result == FAILURE )
printf("\n No storage\n");
return result;
}
The features of this code worth noting
The mutex lock and unlock appear only once in the function, and there
are no return statements in the critical section. This makes it
clear that the mutex will always be unlocked.
The printf is outside of the critical section. printf is
relatively slow, and any function that uses a mutex should hold the
mutex for as little time as possible.
IMO the function shouldn't return a count, but rather should only
return a bool indicating success or failure. Any code that needs
to know how many entries are in the array should lock the mutex and
examine the count directly.
Nothing wrong with the previous answers, but there is a better way. A mutex is not needed.
int func(char ctr) {
int c = interlocked_increment(&count);
if (c >= sizeof(arr)) {
printf("\n No storage\n");
interlocked_decrement(&count);
return -1;
}
arr[c-1] = ctr;
return c;
}
This depends on the availability of interlocked increment and decrement functions, which have to be provided by your operating system or third party library.
Every value of c that is within range is guaranteed to be one not seen by any other thread, and is therefore a valid slot in arr, and no thread will miss a slot if there is one available. The order of storing the value is indeterminate, but that is true of most of the other solutions too. The maximum value reached by count if many threads are competing for storage is also indeterminate, and if that is an issue a different approach might be needed. The behaviour if count is decremented is another unknown. This stuff is hard, and it's always possible to add additional constraints to make it harder.
Just to point out that there is another possible implementation based on a CSET (Check and Set) function. This is a function that checks whether some location is equal to a value and if so sets it to another value atomically, returning true if so. It avoids some of the criticism leveled at the above function.
int func(char ctr) {
for (;;) {
int c = count;
if (c >= sizeof(arr)) {
printf("\n No storage\n");
return -1;
}
if (CSET(&count, c, c+1)) {
arr[c] = ctr;
return c;
}
}
}
The C++ standard atomic operations library contains a set of atomic_compare_exchange functions which should serve the purpose, if available.
I'm trying to implement a program that uses a thread to read data from a file and write to a buffer of an arbitrary size, while two other threads read info from this buffer. Everything works fine, except for when I specify a buffer size of 1. When I do that, everything locks up. I'm more or less adapting the classic "consumer/producer" example from here Here's my code:
Struct I'm using:
struct prodcons {
char** buffer; pthread_mutex_t lock;
pthread_cond_t notempty; pthread_cond_t notfull;
int readpos, writepos, finished;
};
My "add to buffer" thread
static void buf_add(struct prodcons *b, char* data) {
/* Add the data after locking the buffer */
pthread_mutex_lock(&b-> lock);
printf("Reader adding %s\n", data);
int err;
/*Wait until buffer is not full*/
while ((b->writepos + 1) % numlines == b->readpos) {
err = pthread_cond_wait(&b->notfull, &b->lock);
if (err != 0) { fprintf(stderr, "cond wait");}
}
/* Needed to stop writes */
if (data != NULL) {
b->buffer[b->writepos] = strdup(data);
} else {
//fprintf(stderr, "End of file reached, adding NULL\n");
b->buffer[b->writepos] = NULL;
}
/* Increments the writing position */
(*b).writepos++;
if ((*b).writepos >= numlines) {
printf("Resetting write position\n");
(*b).writepos = 0;
}
/* Unlock */
pthread_cond_signal(&b->notempty);
pthread_mutex_unlock(&b->lock);
}
Here's what output looks like:
Reader adding 64.233.173.85
And then it just hangs. It's clear that it's never getting beyond that first while loop. It works with any other size, but not with 1. what would be the best way of implement a fix for this? If this helps, here is my "get from buffer" method.
static void *read_from_buffer(struct prodcons *b) {
pthread_mutex_lock(&b -> lock);
/* We have to wait for the buffer to have something in it */
while ((*b).writepos == (*b).readpos) {
pthread_cond_wait(&b->notempty, &b->lock);
}
/* Set the thread delay */
thread_delay.tv_sec = threaddelay / 100000;
thread_delay.tv_nsec = 1000*threaddelay%100000;
char *t = NULL;
/* Read the data and advance the reader pointer */
if ((*b).buffer[(*b).readpos] != NULL) {
t = (char*)malloc(strlen ((*b).buffer[(*b).readpos] ) + 1);
strcpy(t, (*b).buffer[(*b).readpos]);
printf("Consumer %u reading from buffer: got %s\n", (unsigned int)pthread_self(), t);
/*At this point, we should probably check is FQDN*/
if (strcmp(t, "-1") == 0) {
(*b).finished = 1;
} else {
nanosleep(&thread_delay, &thread_delay_rem);
check_cache(t, &cache);
}
}
/* We have to adjust the reading position */
(*b).readpos++;
if ( (*b).readpos >= numlines) {
(*b).readpos = 0;
}
/*Need to signal and unlock */
pthread_cond_signal (&b->notfull);
pthread_mutex_unlock(&b->lock);
return t;
}
I'm sure there is a fairly simple fix to handle this edge case, but I can't seem to figure it out. Any suggestions would be much appreciated!
EDIT: I also initialize my buffer like so:
static void init(struct prodcons *temp) {
(*temp).buffer = (char**)malloc(numlines * sizeof(char*));
Not having stepped through your code, but you're writing a terminating NUL '\0' byte, and that would take up an entire 1-byte buffer. The writer waits forever for space in the buffer.
while ((b->writepos + 1) % numlines == b->readpos) { /* always satisfied */
I am trying to solve a consumer/producer problem, and I have to create three different classes.
The main class includes the creation of threads, and consumer/producer logic
the other two classes are
A header file for a ring buffer
A file containing the implementation of the ring buffer
I'm getting the following errors when trying to compile:
ringbuf.c: In function ‘rb_init’:
ringbuf.c:10: warning: incompatible implicit declaration of built-in function ‘malloc’
ringbuf.c:10: error: invalid application of ‘sizeof’ to incomplete type ‘struct ringbuf_t’
ringbuf.c:12: error: dereferencing pointer to incomplete type
I many other errors, but I can handle them myself once I get through this one.
this is the header file for the buffer:
struct ringbuf_t
{
pthread_mutex_t mutex; /* lock to protect from simutaneous access to the buffer */
pthread_cond_t cond_full; /* producer needs to wait when the buffer is full */
pthread_cond_t cond_empty; /* consumer needs to wait when the buffer is empty */
int bufsiz; /* size of the buffer; you may use an empty slot to differentiate the situation the buffer is full or empty */
int front; /* index of the first element */
int back; /* index next to the last element (or index to the first empty slot) */
int count; //keeps track of the number of elements in the buffer
char* buf; /* buffer itself */
};
/* return the pointer to the newl created and initialized ring buffer of the given size */
extern struct ringbuf_t* rb_init(int bufsiz);
/* reclaim the ring buffer */
extern void rb_finalize(struct ringbuf_t* rb);
/* return the current number of elements in the buffer */
extern int rb_size(struct ringbuf_t* rb);
/* return non-zero (true) if the buffer is currently full */
extern int rb_is_full(struct ringbuf_t* rb);
/* return non-zero (true) if the buffer is currently empty */
extern int rb_is_empty(struct ringbuf_t* rb);
/* insert (i.e., append) a character into the buffer; if the buffer is full, the caller thread will be blocked */
extern void rb_insert(struct ringbuf_t* rb, int c);
/* retrieve a character at the front of the ring buffer; if the buffer is empty, the caller thread will be blocked */
extern int rb_remove(struct ringbuf_t* rb);
and this is the implementation of the buffer:
#include <malloc.h>
#include <stdio.h>
struct ringbuf_t
{
pthread_mutex_t mutex; /* lock to protect from simutaneous access to the buffer */
pthread_cond_t cond_full; /* producer needs to wait when the buffer is full */
pthread_cond_t cond_empty; /* consumer needs to wait when the buffer is empty */
int bufsiz; /* size of the buffer; you may use an empty slot to differentiate the situation the buffer is full or empty */
int front; /* index of the first element */
int back; /* index next to the last element (or index to the first empty slot) */
int count; //keeps track of the number of elements in the buffer
char* buf; /* buffer itself */
};
struct ringbuf_t* rb_init(int bufsiz)
{
struct ringbuf_t* buffer = (struct ringbuf_t*)malloc(sizeof(struct ringbuf_t));
buffer->bufsiz = bufsiz;
buffer->front = 0;
buffer->back = 0;
buffer->count = 0;
}
/* reclaim the ring buffer */
void rb_finalize(struct ringbuf_t* rb)
{
free(rb);
pthread_mutex_destroy(&rb->mutex);
printf("\nnotice - ring buffer finalized");
}
/* return the current number of elements in the buffer */
int rb_size(struct ringbuf_t* rb)
{
return (rb->count);
}
/* return non-zero (true) if the buffer is currently full */
int rb_is_full(struct ringbuf_t* rb)
{
return ((rb->count) == rb->bufsiz);
}
/* return non-zero (true) if the buffer is currently empty */
int rb_is_empty(struct ringbuf_t* rb)
{
return ((rb->count) == 0);
}
/* insert (i.e., append) a character into the buffer; if the buffer is full, the caller thread will be blocked */
void rb_insert(struct ringbuf_t* rb, int c)
{
char* temp;
if(rb->count < rb->bufsiz)
{
if(rb->count == 0)
{
rb->front = 0;
rb->back = 0;
rb->buf = c;
rb->count++;
}
else
{
if(rb->front < (rb->bufsiz - 1))
{
temp = rb->buf;
temp = temp + rb->front + 1;
temp = c;
rb->front++;
}
else if(rb->front == (rb->bufsiz -1))
{
rb->front = 0;
rb->buf = c;
}
}
}
else
{
printf("\nerror - trying to insert into full buffer");
}
}
/* retrieve a character at the tail (back) of the ring buffer; if the buffer is empty, the caller thread will be blocked */
int rb_remove(struct ringbuf_t* rb)
{
if(rb->count != 0)
{
count--;
if(rb->back < (rb->bufsiz-1)
{
rb->back++;
}
else if(rb->back == (rb->bufsiz -1))
{
rb->back = 0;
}
}
else
{
printf("\nerror - trying to remove from empty buffer");
}
}
this is the main class:
#include <stdio.h>
#include <pthread.h>
#include <ringbuf.h>
//creating a static ring buffer
struct ringbuf* mybuffer = rb_init(10);
int thisChar;
/*
consumer thread, reads one character at a time, sets the lock when addinga character to the ring buffer
while locked it checks if the buffer is full, waits for a slot, and then continues.
*/
void* consumer(void* arg)
{
printf("consumer started");
while(thisChar != EOF)
{
pthread_mutex_lock(&(mybuffer->mutex));
while(rb_is_empty(mybuffer))
pthread_cond_wait(&(mybuffer->cond_full), &(mybuffer->mutex));
printf("%s", (char)rb_remove(mybuffer));
pthread_cond_signal(&(mybuffer->cond_empty));
pthread_mutex_unlock(&(mybuffer->mutex));
}
}
/*
producer thread, takes one character at a time from the buffer, (while the buffer is not empty)
and prints it to the screen.
*/
void* producer(void* arg)
{
printf("producer started");
while ((thisChar = getchar()) != EOF)
{
pthread_mutex_lock(&(mybuffer->mutex));
while(rb_is_full(mybuffer))
pthread_cond_wait(&(mybuffer->cond_empty), &(mybuffer->mutex));
rb_insert(mybuffer, thisChar);
pthread_cond_signal(&(mybuffer->cond_full));
pthread_mutex_unlock(&(mybuffer->mutex));
}
}
int main()
{
//declaring threads
pthread_t t0, t1;
//creating threads as condumer, producer
p_thread_create(&t0, NULL, consumer, (void*)mybuffer);
p_thread_create(&t1, NULL, producer, (void*)mybuffer);
pthread_join(t0, NULL);
pthread_join(t1, NULL);
rb_finalize(mybuffer);
return 0;
}
I'm missing some stuff, but I need to get through this first! please help!
Replace your #include <malloc.h> lines with #include <stdlib.h>. That will fix the errors you have pasted here (and probably many many more). When you do that, go back through your code and remove all the casts in your calls to malloc(3):
struct ringbuf_t* buffer = (struct ringbuf_t*)malloc(sizeof(struct ringbuf_t));
That (struct ringbuf_t*) hasn't been necessary since roughly 1989, when function prototypes were pushed into the language.
See also:
Should I use #include in headers?
What are extern variables in C?
ringbuf.h
Your ringbuf.h header should be self-contained and idempotent. It should, therefore, include <pthread.h>.
#ifndef RINGBUF_H_INCLUDED
#define RINGBUF_H_INCLUDED
#include <pthread.h>
struct ringbuf_t
{
pthread_mutex_t mutex; /* lock to protect from simutaneous access to the buffer */
pthread_cond_t cond_full; /* producer needs to wait when the buffer is full */
pthread_cond_t cond_empty; /* consumer needs to wait when the buffer is empty */
int bufsiz; /* size of the buffer; you may use an empty slot to differentiate the situation the buffer is full or empty */
int front; /* index of the first element */
int back; /* index next to the last element (or index to the first empty slot) */
int count; //keeps track of the number of elements in the buffer
char* buf; /* buffer itself */
};
/* return the pointer to the newl created and initialized ring buffer of the given size */
extern struct ringbuf_t* rb_init(int bufsiz);
/* reclaim the ring buffer */
extern void rb_finalize(struct ringbuf_t* rb);
/* return the current number of elements in the buffer */
extern int rb_size(struct ringbuf_t* rb);
/* return non-zero (true) if the buffer is currently full */
extern int rb_is_full(struct ringbuf_t* rb);
/* return non-zero (true) if the buffer is currently empty */
extern int rb_is_empty(struct ringbuf_t* rb);
/* insert (i.e., append) a character into the buffer; if the buffer is full, the caller thread will be blocked */
extern void rb_insert(struct ringbuf_t* rb, int c);
/* retrieve a character at the front of the ring buffer; if the buffer is empty, the caller thread will be blocked */
extern int rb_remove(struct ringbuf_t* rb);
#endif /* RINGBUF_H_INCLUDED */
Were it my header, I'd have an extra line:
typedef struct ringbuf_t ringbuf_t;
and I'd edit the function prototypes to lose the struct keyword.
The advantage of this is that anyone can include ringbuf.h and it will simply work for them.
ringbuf.c
It is crucial that the implementation file uses its own header; that gives you the necessary cross-checking that the header accurately reflects what is implemented. It should also be the first header included; this gives a simple but effective check that the header is self-contained.
You should not use <malloc.h> unless you are using its extended features. The <stdlib.h> declares malloc() et al and should be used unless you know which extra functions are available in <malloc.h> and you actually use them.
This leads to:
#include "ringbuf.h"
#include <stdio.h>
#include <stdlib.h>
struct ringbuf_t* rb_init(int bufsiz)
{
struct ringbuf_t* buffer = (struct ringbuf_t*)malloc(sizeof(struct ringbuf_t));
buffer->bufsiz = bufsiz;
buffer->front = 0;
buffer->back = 0;
buffer->count = 0;
}
/* reclaim the ring buffer */
void rb_finalize(struct ringbuf_t* rb)
{
free(rb);
pthread_mutex_destroy(&rb->mutex);
printf("\nnotice - ring buffer finalized");
}
/* return the current number of elements in the buffer */
int rb_size(struct ringbuf_t* rb)
{
return (rb->count);
}
/* return non-zero (true) if the buffer is currently full */
int rb_is_full(struct ringbuf_t* rb)
{
return ((rb->count) == rb->bufsiz);
}
/* return non-zero (true) if the buffer is currently empty */
int rb_is_empty(struct ringbuf_t* rb)
{
return ((rb->count) == 0);
}
/* insert (i.e., append) a character into the buffer; if the buffer is full, the caller thread will be blocked */
void rb_insert(struct ringbuf_t* rb, int c)
{
char* temp;
if(rb->count < rb->bufsiz)
{
if(rb->count == 0)
{
rb->front = 0;
rb->back = 0;
rb->buf = c;
rb->count++;
}
else
{
if(rb->front < (rb->bufsiz - 1))
{
temp = rb->buf;
temp = temp + rb->front + 1;
temp = c;
rb->front++;
}
else if(rb->front == (rb->bufsiz -1))
{
rb->front = 0;
rb->buf = c;
}
}
}
else
{
printf("\nerror - trying to insert into full buffer");
}
}
/* retrieve a character at the tail (back) of the ring buffer; if the buffer is empty, the caller thread will be blocked */
int rb_remove(struct ringbuf_t* rb)
{
if(rb->count != 0)
{
count--;
if(rb->back < (rb->bufsiz-1)
{
rb->back++;
}
else if(rb->back == (rb->bufsiz -1))
{
rb->back = 0;
}
}
else
{
printf("\nerror - trying to remove from empty buffer");
}
}
You should probably use fprintf(stderr, ...) rather than printf() for diagnostics, and you should consider how to turn them off at run-time (or, more likely, how to turn them on).
Note that it is conventional to put system-provided headers in angle brackets (hence <stdio.h>) and user-provided headers in double quotes (hence "ringbuf.h").
Your rb_init() function should initialize the structure completely. That means that the mutex and the two condition variables should both be initialized properly. It also needs to either initialize (zero) the buf member or allocate the appropriate amount of space - more likely the latter. Your code should check that the allocations succeed, and only use the allocated space if it does.
You should also review whether it is appropriate to make the producer and consumer threads manipulate the mutex and condition variables. If they are bundled with the structure, the functions bundled with the structure should do what is necessary with the mutexes and conditions. This would allow you to simplify the producer and consumer to just call the ring buffer functions. Clearly, the main() will still launch the two threads, but if you get your abstraction right, the threads themselves won't need to dink with mutexes and conditions directly; the ring buffer library code will do that correctly for the threads. One of the advantages of this is that your library can get the operations right, once, and all consumers benefit. The alternative is to have every producer and consumer handle the mutexes and conditions - which magnifies the opportunities to get it wrong. In a classroom situation where you won't use the abstraction again after this exercise, the proper separation and encapsulation is not so critical, but in professional code, it is crucial that the library make it easy for people to use the code correctly and hard for them to make mistakes.
main.c
In C, you cannot initialize a global variable with a function call - in C++, you can.
Hence, this won't compile in C:
//creating a static ring buffer
struct ringbuf_t *mybuffer = rb_init(10);
You should use:
struct ringbuf_t *mybuffer = 0;
and then, in main() or a function called from main() - directly or indirectly - do the function call:
mybuffer = rb_init(10);
This would be before you do any work creating the threads. When your rb_init() code initializes the mutex and condition variables, your main() will be able to go ahead as written.
Until then, you have a good deal of cleanup to do.
Disclaimer I have not compiled the code to see what the compiler witters about.
Note If you use GCC but don't compile with at least -Wall and preferably -Wextra too (and clean up any (all) the warnings), you are missing out on a very important assistant. I work with retrograde code bases where I have to worry about -Wmissing-prototypes -Wold-style-definition -Wstrict-prototypes too. Using -Werror can be helpful; it forces you to clean up the warnings.