Passing Data to Multi Threads - c

I study this code from some book:
#include <pthread.h>
#include <stdio.h>
/* Parameters to print_function. */
struct char_print_parms {
/* The character to print. */
char character;
/* The number of times to print it. */
int count;
};
/* Prints a number of characters to stderr, as given by PARAMETERS,
which is a pointer to a struct char_print_parms. */
void* char_print(void* parameters) {
/* Cast the cookie pointer to the right type. */
struct char_print_parms* p = (struct char_print_parms*) parameters;
int i;
for (i = 0; i < p->count; ++i)
fputc(p->character, stderr);
return NULL;
}
/* The main program. */
int main() {
pthread_t thread1_id;
pthread_t thread2_id;
struct char_print_parms thread1_args;
struct char_print_parms thread2_args;
/* Create a new thread to print 30,000 ’x’s. */
thread1_args.character = 'x';
thread1_args.count = 30000;
pthread_create(&thread1_id, NULL, &char_print, &thread1_args);
/* Create a new thread to print 20,000 o’s. */
thread2_args.character = 'o';
thread2_args.count = 20000;
pthread_create(&thread2_id, NULL, &char_print, &thread2_args);
usleep(20);
return 0;
}
after running this code, I see different result each time. and some time corrupted result. what is wrong and what the correct way to do that?

Add:
pthread_join( thread1_id, NULL);
pthread_join( thread2_id, NULL);
to the bottom of your code, before the return in main. Your prosess ends before your threads can complete. A sleep of 20 micro seconds is not enough to let your threads complete executing. Safer to wait for the threads to return.

Related

malloc() is returning the same address multiple times, even when I haven't used free()

EDIT: I did use free(), ignore the title.
The gist is that every time malloc() is called, the address 0x8403620
is returned, which I found out using Gdb.
tellers[i] = create_teller(0, i, NULL);
I first use malloc() on line 72 to create 3 teller structures. The first addressed returned, visible through Gdb, is 0x84003620. The second is
0x84033a0, the third 0x84034e0. Everything seems fine.
clients[i] = create_client(0, i, -1, -1);
Then I use malloc() on line 77 with the create_client() function to
create 100 clients. The first address, assigned to client[0], is ...
0x8403620. The same as tellers[0]. It gets worse. The next address
returned from malloc() is 0x8403620 again for when i = 1, and so
on for i = 3, 4, ..., 99.
It isn't inherently the create_client() or the create_teller() functions, but
instead the malloc() function itself.
This is simply a very odd situation.
Now, I'd like to ask: Am I using malloc() wrong? Or is my version of malloc() bugged and should I somehow reinstall whatever it is? It's most likely my code since it works for creating the tellers, just not for the clients.
Here is the full code:
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
#include <assert.h>
typedef struct teller teller_t;
typedef struct client client_t;
teller_t * create_teller (pthread_t thread_id, int id, client_t *assigned_client);
client_t * create_client (pthread_t thread_id, int id, int operation, int amount);
void * run_teller (void *arg);
void * run_client (void *arg);
/* types of operations */
#define DEPOSIT 0
#define WITHDRAW 1
#define NUM_TELLERS 3
#define NUM_CLIENTS 100
struct client {
pthread_t thread_id;
int id;
int operation;
int amount;
};
struct teller {
pthread_t thread_id;
int id;
bool available;
client_t *assigned_client;
};
client_t *clients[100];
teller_t *tellers[3];
/* only 2 tellers at a time can access */
sem_t safe;
/* only 1 teller at a time can access */
sem_t manager;
/* amount of tellers available, at most 3 */
sem_t line; /* rename to available? */
/* each teller waiting for a client to be assigned to them */
sem_t wait_for_client[3];
int
main (int argc, char **argv) {
(void) argc;
(void) argv;
srand(time(NULL));
/* This also tells us how many clients have been served */
int client_index = 0;
sem_init(&safe, 0, 2);
sem_init(&manager, 0, 1);
sem_init(&line, 0, 0);
for (int i = 0; i < 3; i++)
sem_init(&wait_for_client[i], 0, 0);
for (int i = 0; i < NUM_TELLERS; i++) {
tellers[i] = create_teller(0, i, NULL);
pthread_create(&tellers[i]->thread_id, NULL, run_teller, (void *) tellers[i]);
}
for (int i = 0; i < NUM_CLIENTS; i++) {
clients[i] = create_client(0, i, -1, -1);
pthread_create(&clients[i]->thread_id, NULL, run_client, (void *) clients[i]);
}
/* DEBUG
for (int i = 0; i < NUM_CLIENTS; i++) {
printf("client %d has id %d\n", i, clients[i]->id);
}
*/
// No threads should get past this point!!!
// ==------------------------------------==
// Should all of this below be handled by the clients instead of main?
while (1) {
if (client_index >= NUM_CLIENTS) {
// TODO:
// tell tellers that there are no more clients
// so they should close, then then close the bank.
break;
}
sem_wait(&line);
for (int i = 0; i < 3; i++) {
if (tellers[i]->available) {
int client_id = clients[client_index]->id;
//printf("client_index = %d\n", client_index); // DEBUG
tellers[i]->assigned_client = clients[client_index++];
tellers[i]->available = false;
printf(
"Client %d goes to Teller %d\n",
client_id,
tellers[i]->id
);
sem_post(&wait_for_client[i]);
break;
}
}
//sem_post(&line); // Is this needed?
}
return EXIT_SUCCESS;
}
teller_t *
create_teller (pthread_t thread_id, int id, client_t *assigned_client) {
teller_t *t = (teller_t *) malloc(sizeof(teller_t));
if (t == NULL) {
printf("ERROR: Unable to allocate teller_t.\n");
exit(EXIT_FAILURE);
}
t->thread_id = thread_id;
t->id = id;
t->available = true;
t->assigned_client = assigned_client;
return t;
}
/* TODO: Malloc returns the same address everytime, fix this */
client_t *
create_client (pthread_t thread_id, int id, int operation, int amount) {
client_t *c = malloc(sizeof(client_t));
if (c == NULL) {
printf("ERROR: Unable to allocate client_t.\n");
exit(EXIT_FAILURE);
}
c->thread_id = thread_id;
c->id = id;
c->operation = operation;
c->amount = amount;
return c;
}
void *
run_teller (void *arg) {
teller_t *t = (teller_t *) arg;
printf("Teller %d is available\n", t->id);
while (1) {
/* tell the line that a teller is available */
sem_post(&line);
/* pass when the line assignes a client to this teller */
sem_wait(&wait_for_client[t->id]);
assert(t->assigned_client != NULL);
if (t->assigned_client->operation == WITHDRAW) {
}
else {
}
}
free(arg);
pthread_cancel(t->thread_id);
return NULL;
}
void *
run_client (void *arg) {
client_t *c = (client_t *) arg;
c->operation = rand() & 1;
printf(
"Client %d waits in line to make a %s\n",
c->id,
((c->operation == DEPOSIT) ? "Deposit" : "Withdraw")
);
free(arg);
pthread_cancel(c->thread_id);
return NULL;
}
Then I use malloc() on line 77 with the create_client() function to create 100 clients.
Not exactly, you create one object, then you spawn a thread that manages that object, run_client() and then repeat. But run_client() basically does nothing except free() your client object! So malloc is totally right returning the same address again, as it is now free memory.
It just happens that your client threads are faster than your main one. Your problem here is that you are freeing the objects from secondary threads while leaving the dangling pointers in the global pointer array. If you use that array for debugging purposes, then nothing is actually wrong here, but if you want to use the client objects somewhen in the future, then you should not free your clients in the first place.

Out of memory in a thread pool in C, Linux

I need to create infinite loop and with a thread pool create for example 200 threads to do the job from infinite loop.
I'm using this thread pool - https://github.com/Pithikos/C-Thread-Pool
In the same time I'm monitoring the server resources (with htop) and see that memory is increasing on 3 megabytes every second until the kernel kills the application.
The code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "thpool.h"
#define MAX_IPv4 256
/* Args for thread start function */
typedef struct {
int octet1;
int octet2;
int octet3;
int octet4;
} args_struct;
/* Thread task */
void task1(void *args) {
args_struct *actual_args = args;
printf("%d.%d.%d.%d\n", actual_args->octet1, actual_args->octet2, actual_args->octet3, actual_args->octet4);
/* Do some job */
sleep(1);
/* Free the args */
free(args);
}
/* Main function */
int main( void ) {
int i=0, j=0, n=0, m=0;
/* Making threadpool n threads */
threadpool thpool = thpool_init(200);
/* Infinite loop start from the certain ip*/
while (1) {
for (i=0; i < MAX_IPv4; ++i) {
for (j=0; j < MAX_IPv4; ++j) {
for (n=0; n < MAX_IPv4; ++n) {
for (m=0; m < MAX_IPv4; ++m) {
/* Heap memory for the args different for the every thread */
args_struct *args = malloc(sizeof *args);
args->octet1 = i;
args->octet2 = j;
args->octet3 = n;
args->octet4 = m;
/* Create thread */
thpool_add_work(thpool, (void*)task1, (void*)args);
}
}
}
}
/* Start from 0.0.0.0 */
i=0;
j=0;
n=0;
m=0;
}
/* Wait until the all threads are done */
thpool_wait(thpool);
/* Destroy the threadpool */
thpool_destroy(thpool);
return 0;
}
How to solve this issue?
Looking at issues for your library ( especially this one about memory consumption ).
There is a recommendation to check the job queue length threadpool.jobqueue.len;
Maybe your code could check after adding your job to the queue
Unfortunately the threadpool is an opaque pointer and you could not access the value directly.
I would recommend adding a function for the threadpool in thpool.c :
int thpool_jobqueue_length(thpool_* thpool_p) {
return thpool->jobqueue->len;
}
And don't forget the declaration in thpool.h
int thpool_jobqueue_length(threadpool);
Then modify your code
const int SOME_ARBITRARY_VALUE = 400
...
thpool_add_work(thpool, (void*)task1, (void*)args);
while( ( thpool_jobqueue_length(thpool) > SOME_ARBITRARY_VALUE ) ) {
sleep(1);
}
...
Looking at the code for thpool_add_work there is some memory use per call (allocating a job record to add to a queue), so as your loop runs forever, it is not surprising that it will run out of memory at some point. You are also allocating memory inside your innermost loop, so that too will help use up all your memory.
Essentially inside your inner loop you are allocating 16 bytes (assuming int is 4) for the args_struct, and thpool_add_work is also allocating 12 bytes (possibly rounded to 16 for alignment purposes).
As you can imagine, that adds up to a lot for your 4 nested loops (which are also run infinitely).

Incorrect output of code after using pthreads

I am working in some code trying to make it use threads, with little success. The code is as follows (using some online tuts)
(1) Create an array to save arguments to pass to each thread.
(2) The struct where thread args are stored
(3) Function each thread executes
(4) Main, where I create each pthread and pass args. end and begin are dummy i and i+1 just for testing purposes.
#include <pthread.h>
struct thread_data* thread_data_array;
struct thread_data{
int thread_id;
int begin;
int end;
};
void *proccessData(void *threadarg)
{
struct thread_data *my_data = (struct thread_data *) threadarg;
int id = my_data->thread_id;
int start = my_data->begin;
int end = my_data->end;
printf("%s%d%s%d%s%d%s","Process: Id ",id," begin: ",start," end: ",end,"\n");
// Init
FILE* fileOut;
// do stuff
fileOut = fopen(someNameUsingId, "w");
// more stuff
fclose(fileOut);
}
int main(int argc, char **argv)
{
int n_th = atoi(argv[1]);
thread_data_array = malloc(n_th*sizeof(struct thread_data));
pthread_t threads[n_th];
int i;
for (i=0; i<n_th; i++)
{
thread_data_array[i].thread_id = i;
thread_data_array[i].begin = i;
thread_data_array[i].end = i+1;
pthread_create(&threads[i], NULL, proccessData,(void *) &thread_data_array[i]);
}
}
What I am getting: Sometimes nothing is printed, Sometimes some ids are printed twice. Files are not created (one in every 5 lets say, files are created, and are empty)
But using this in main works as I am expecting, id 1 from 0 to 1 is printed, id 2 from 1 to 2, and both files are created and have the right content
thread_data_array[0].thread_id = 1;
thread_data_array[0].begin = 0;
thread_data_array[0].end = 1;
proccessData((void *) &thread_data_array[0]);
thread_data_array[1].thread_id = 2;
thread_data_array[1].begin = 1;
thread_data_array[1].end = 2;
proccessData((void *) &thread_data_array[1]);
Can someone point out what am I doing wrong, and how to solve it?
Thanks in advance
Your main is terminating before the other threads. main is special, as returning from it is equivalent to call exit.
Either use pthread_exit to end main or use pthread_join to wait for the termination of the other threads.
pthread_join will solve the issue, call it for each created thread in main. pthread_join() function is called to wait for the threads to complete. If main is done before threads are done, they will die before they finished their job.

multi-threaded consumer,producer C code, not performing?

I'm trying to do a simple multi-threaded consumer/producer, where multiple reader and writer thread, read from a file to the buffer and then from buffer back into a file. It should be thread safe. however, it is not performing as i expected. It halts half way but everytime on a different line?
Please help me understand what I am doing wrong?!?
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
//TODO Define global data structures to be used
#define BUF_SIZE 5
FILE *fr;
FILE *to; /* declare the file pointer */
struct _data {
pthread_mutex_t mutex;
pthread_cond_t cond_read;
pthread_cond_t cond_write;
int condition;
char buffer[BUF_SIZE];
int datainbuffer;
}dc1 = {
PTHREAD_MUTEX_INITIALIZER,PTHREAD_COND_INITIALIZER,PTHREAD_COND_INITIALIZER,0,{0},0
};
void *reader_thread(void *arg) {
//TODO: Define set-up required
struct _data *d = (struct _data *)arg;
int killreaders = 0;
while(1) {
//TODO: Define data extraction (queue) and processing
pthread_mutex_lock(&d->mutex);
while (d->condition == 0 || d->datainbuffer<=0){
pthread_cond_wait( &d->cond_read, &d->mutex );
if(killreaders == 1){
pthread_mutex_unlock(&d->mutex);
pthread_cond_signal(&d->cond_read);
pthread_cond_signal(&d->cond_write);
return NULL;
}
}
d->condition = 0;
int i;
char res;
//if the buffer is not full, that means the end of file is reached and it time to kill the threads remaining.
if(d->datainbuffer!=BUF_SIZE)
killreaders = 1;
for (i=0; i<(sizeof d->datainbuffer); i++) {
res = d->buffer[i];
printf("to file:%c",res);
fputc(res, to);
}
d->datainbuffer = 0;
pthread_mutex_unlock(&d->mutex);
pthread_cond_signal( &d->cond_write );
}
return NULL;
}
void *writer_thread(void *arg) {
//TODO: Define set-up required
struct _data *d = (struct _data *)arg;
char * pChar;
int killwriters = 0;
while(1){
pthread_mutex_lock(&d->mutex);
while( d->condition == 1 || d->datainbuffer>0){
pthread_cond_wait( &d->cond_write, &d->mutex );
if(killwriters==1){
pthread_mutex_unlock(&d->mutex);
pthread_cond_signal(&d->cond_write);
pthread_cond_signal(&d->cond_read);
return NULL;
}
}
d->condition = 1;
int i;
char rc;
for (i = 0; i < BUF_SIZE; i++){
if((rc = getc(fr)) == EOF){
killwriters = 1;
pthread_mutex_unlock(&d->mutex);
pthread_cond_signal(&d->cond_read);
return NULL;
}
d->datainbuffer = i+1;
d->buffer[i] = rc;
printf("%c",rc);
}
int m = 0;
pthread_mutex_unlock(&d->mutex);
pthread_cond_signal(&d->cond_read);
}
return NULL;
}
#define M 10
#define N 20
int main(int argc, char **argv) {
struct _data dc=dc1;
fr = fopen ("from.txt", "rt"); /* open the file for reading */
if (fr == NULL)
{
printf("Could not open file!");
return 1;
}
to = fopen("to.txt", "wt");
int i;
pthread_t readers[N];
pthread_t writers[M];
for(i = 0; i < N; i++) {
pthread_create(&readers[i], NULL, reader_thread, (void*)&dc);
}
for(i = 0; i < M; i++) {
pthread_create(&writers[i], NULL, writer_thread, (void*)&dc);
}
fclose(fr);
fclose(to);
return 0;
}
any suggestion is appreciated!
Your threads are reading from and writing to files, which you open & close in main. But main doesn't explicitly wait for the threads to finish before closing those files.
In addition to the problem pointed out by Scott Hunter, your readers and writers do all their "real work" while holding the mutex, defeating the point of having more than one thread in the first place.
Readers should operate as follows:
1) Acquire mutex.
2) Block on the condition variable until work is available.
3) Remove work from queue, possibly signal condition variable.
4) Release mutex.
5) Process the work.
6) Go to step 1.
Writers should operate as follows:
1) Get the information we need to write.
2) Acquire the mutex.
3) Block on the condition variable until there is space on the queue.
4) Place information in the queue, possibly signal condition variable.
5) Release the mutex.
6) Go to step 1.
Notice both threads do the "real work" without holding the mutex? Otherwise, why have multiple threads if only one of them can do work at a time?
I'm not sure whether my answer is going to help you or not.. but I'm going to give my best by giving you some reference code.
I have written a similar program (except that it does not write to the file, instead display the queue-/produced-/consumed- items in the stdout). It can be found here - https://github.com/sangeeths/pc . I have separated the command-line processing and queue logic into a separate files.
Hope this helps!

I'm having a hard time having a struct work using multiple files. Please help?

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.

Resources