circular buffer with read only access from reader - c

I have a problem with a circular buffer I want to build where the reader only has read-only access. In order to achieve a smooth rollover, I have the writer to set an id in iterator+1 of the rollover data structure to 0 for which I check in with the reader. My algorith seems to work fine until the first roll over, then for some reason, the resder will read 0 from the id which the writer obviously has set.
i have some compileable example code to demonstrate the problem right here:
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#define NUM_ALM 5
#define ERROR -1
#define OK 0
//even IDs = alarm active
//odd IDs = alarm clear
enum alarmid {
BFD_ACT = 0x02,
BFD_CLR = 0x03,
LOS_ACT = 0x0C
};
typedef struct alarm_s {
long timestamp;
int alarmid;
int arg1;
int arg2;
}alarm_t;
int alarm_add(int id, int arg1, int arg2);
int next_alarm_read(alarm_t *res);
void *alarm_reader(void *arg);
static alarm_t *roller;
pthread_cond_t cv;
pthread_mutex_t mutex;
int main (void)
{
int i =0;
alarm_t dat;
pthread_t reader;
int ret;
roller = calloc(NUM_ALM,sizeof(alarm_t));
printf("allocated memory: %lukB\n",(sizeof(alarm_t)*NUM_ALM)/1024);
for (i = 1; i< NUM_ALM; i++){
alarm_add(LOS_ACT,i,0);
}
ret = pthread_create(&reader,NULL,alarm_reader,NULL);
if (ret){
printf("Error - pthread_create() return code: %d\n",ret);
return ERROR;
}
sleep(1);
alarm_add(BFD_ACT,8,0);
alarm_add(BFD_ACT,8,0);
alarm_add(BFD_ACT,8,0);
alarm_add(BFD_ACT,8,0);
alarm_add(BFD_CLR,8,0);
alarm_add(BFD_CLR,8,0);
alarm_add(BFD_CLR,8,0);
alarm_add(BFD_CLR,8,0);
alarm_add(BFD_ACT,8,0);
pthread_join(reader,NULL);
}
void *alarm_reader(void *arg)
{
static alarm_t dat={0};
int err = 0;
while(err <= 2)
{
if (next_alarm_read(&dat)== OK)
printf("read alarm id %d, arg1 %d,arg2 %d\n",dat.alarmid,dat.arg1,dat.arg2);
else{
printf("alarm_reader() next_alarm_read() returned ERROR, wait\n");
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cv, &mutex);
pthread_mutex_unlock(&mutex);
err++;
}
}
printf("alarm_reader exit!\n");
}
int alarm_add(int id, int arg1, int arg2)
{
static int i = 0;
alarm_t dat={0};
if (i<NUM_ALM){
dat.timestamp = time(NULL);
dat.alarmid = id;
dat.arg1 = arg1;
dat.arg2 = arg2;
if (&roller[i]){
memcpy(&roller[i],&dat,sizeof(alarm_t));
if (i+1<NUM_ALM)
roller[i+1].alarmid = 0;
else
roller[0].alarmid = 0;
pthread_cond_signal(&cv);
printf("added id %d, arg1 %d, arg2 %d #%d\n",roller[i].alarmid,roller[i].arg1,roller[i].arg2,i);
i++;
}
} else {
i = 0;
}
return 0;
}
int next_alarm_read(alarm_t *res)
{
static int i = 0;
static long prev_time = 0;
if (!res)
return ERROR;
if (i<NUM_ALM)
{
if (roller[i].alarmid!=0){
printf("next_alarm_read() reading #%d\n",i);
res->timestamp = roller[i].timestamp;
res->alarmid = roller[i].alarmid;
res->arg1 = roller[i].arg1;
res->arg2 = roller[i].arg2;
prev_time = roller[i].timestamp;
i++;
} else {
printf("next_alarm_read() #%d is %d,return ERROR\n",i,roller[i].alarmid);
return ERROR;
}
} else {
i = 0;
}
return OK;
}
Where the outpout looks like:
added id 12, arg1 1, arg2 0 #0
added id 12, arg1 2, arg2 0 #1
added id 12, arg1 3, arg2 0 #2
added id 12, arg1 4, arg2 0 #3
next_alarm_read() reading #0
read alarm id 12, arg1 1,arg2 0
next_alarm_read() reading #1
read alarm id 12, arg1 2,arg2 0
next_alarm_read() reading #2
read alarm id 12, arg1 3,arg2 0
next_alarm_read() reading #3
read alarm id 12, arg1 4,arg2 0
next_alarm_read() #4 is 0,return ERROR
alarm_reader() next_alarm_read() returned ERROR, wait
added id 2, arg1 8, arg2 0 #4
added id 2, arg1 8, arg2 0 #0
added id 2, arg1 8, arg2 0 #1
added id 3, arg1 8, arg2 0 #2
added id 3, arg1 8, arg2 0 #3
added id 3, arg1 8, arg2 0 #4
added id 2, arg1 8, arg2 0 #0
next_alarm_read() reading #4
read alarm id 3, arg1 8,arg2 0
read alarm id 3, arg1 8,arg2 0
next_alarm_read() reading #0
read alarm id 2, arg1 8,arg2 0
next_alarm_read() #1 is 0,return ERROR
alarm_reader() next_alarm_read() returned ERROR, wait
the bottom print for next_alarm_read() #1 is 0,return ERROR is wrong, the id should be 2. Why does this not work as intended I'nm wondering?

A few issues:
I'm not sure what if (&roller[i]) is supposed to do/mean.
The sleep in main isn't really needed and I suspect it's an attempt to ameliorate the other issues below.
alarm_add will drop an entry at the rollover point.
Also, it may overrun the reader and overwrite entries before the reader can see the entries (i.e. a race condition).
The reader and writer both need to see each others current queue indexes (i.e. they shouldn't be function scoped static) to prevent overrun/race
There should be two condition variables and not just one:
The writer detects the queue is full and needs to block until the reader has drained an entry
The reader detects an empty queue and needs to block until the writer adds a new entry
Here's a refactored version of your code that should address these issues. I've added some debug code. It may not be perfect [and may err on the side of conservatism], but it should get you a bit further [please pardon the gratuitous style cleanup]:
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#define NUM_ALM 5
#define ERROR -1
#define OK 0
double tvzero;
//even IDs = alarm active
//odd IDs = alarm clear
enum alarmid {
BFD_ACT = 0x02,
BFD_CLR = 0x03,
LOS_ACT = 0x0C
};
typedef struct alarm_s {
long timestamp;
int alarmid;
int arg1;
int arg2;
} alarm_t;
void alarm_add(int id, int arg1, int arg2);
int next_alarm_read(alarm_t * res);
void *alarm_reader(void *arg);
static alarm_t *roller;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// reader variables
pthread_cond_t cv_notempty; // writer signals when queue not empty
volatile int need_notempty; // reader sets this before waiting
volatile int idxdeq; // reader's queue index
// writer variables
pthread_cond_t cv_notfull; // reader signals when queue not full
volatile int need_notfull; // writer sets this before waiting
volatile int idxenq; // writer's queue index
volatile int stopall;
double
tvgetf(void)
{
struct timespec ts;
double sec;
clock_gettime(CLOCK_REALTIME,&ts);
sec = ts.tv_nsec;
sec /= 1e9;
sec += ts.tv_sec;
sec -= tvzero;
return sec;
}
#define DBG(_reason) \
dbg(_reason)
void
dbg(const char *reason)
{
double tvnow;
tvnow = tvgetf();
printf("[%.9f] %s\n",tvnow,reason);
}
int
main(void)
{
int i = 0;
pthread_t reader;
int ret;
tvzero = tvgetf();
roller = calloc(NUM_ALM, sizeof(alarm_t));
printf("allocated memory: %lukB\n", (sizeof(alarm_t) * NUM_ALM) / 1024);
// NOTE: queuing more than a full queue here will cause writer to block
// forever because reader is not yet started
for (i = 1; i < NUM_ALM; i++) {
alarm_add(LOS_ACT, i, 0);
}
ret = pthread_create(&reader, NULL, alarm_reader, NULL);
if (ret) {
printf("Error - pthread_create() return code: %d\n", ret);
return ERROR;
}
#if 0
sleep(1);
#endif
alarm_add(BFD_ACT, 8, 0);
alarm_add(BFD_ACT, 8, 0);
alarm_add(BFD_ACT, 8, 0);
alarm_add(BFD_ACT, 8, 0);
alarm_add(BFD_CLR, 8, 0);
alarm_add(BFD_CLR, 8, 0);
alarm_add(BFD_CLR, 8, 0);
alarm_add(BFD_CLR, 8, 0);
alarm_add(BFD_ACT, 8, 0);
// tell reader that all items are queued and it should stop when it
// processes the final item
pthread_mutex_lock(&mutex);
stopall = 1;
if (need_notempty)
pthread_cond_signal(&cv_notempty);
pthread_mutex_unlock(&mutex);
pthread_join(reader, NULL);
return 0;
}
// RETURNS: queue index to process (-1=empty)
int
queue_notempty(void)
{
int curidx;
do {
curidx = idxdeq;
// queue is empty
if (curidx == idxenq) {
curidx = -1;
break;
}
// advance dequeue index
idxdeq += 1;
idxdeq %= NUM_ALM;
} while (0);
return curidx;
}
// RETURNS: queue index to use (-1=full)
int
queue_notfull(void)
{
int nxtidx;
int curidx;
do {
// get current index
curidx = idxenq;
// advance to next slot (wrapping if necessary)
nxtidx = curidx;
nxtidx += 1;
nxtidx %= NUM_ALM;
// queue is full
if (nxtidx == idxdeq) {
curidx = -1;
break;
}
// store back adjusted index
idxenq = nxtidx;
} while (0);
return curidx;
}
void *
alarm_reader(void *arg)
{
alarm_t dat = { 0 };
while (1) {
if (next_alarm_read(&dat))
break;
printf("read alarm id %d, arg1 %d,arg2 %d\n",
dat.alarmid, dat.arg1, dat.arg2);
}
printf("alarm_reader exit!\n");
return (void *) 0;
}
void
alarm_add(int id, int arg1, int arg2)
{
int curidx;
alarm_t *rol;
pthread_mutex_lock(&mutex);
while (1) {
curidx = queue_notfull();
// have an open slot -- store item into it
if (curidx >= 0) {
rol = &roller[curidx];
rol->timestamp = time(NULL);
rol->alarmid = id;
rol->arg1 = arg1;
rol->arg2 = arg2;
printf("added id %d, arg1 %d, arg2 %d #%d\n",
rol->alarmid, rol->arg1, rol->arg2, curidx);
// unblock reader if necessary
if (need_notempty) {
DBG("writer signal notempty");
need_notempty = 0;
pthread_cond_signal(&cv_notempty);
}
break;
}
// queue is full -- wait for reader to free up some space
DBG("writer need_notfull");
need_notfull = 1;
pthread_cond_wait(&cv_notfull,&mutex);
DBG("writer wakeup");
}
pthread_mutex_unlock(&mutex);
}
// RETURNS: 1=stop, 0=normal
int
next_alarm_read(alarm_t *res)
{
//static long prev_time = 0;
int curidx;
alarm_t *rol;
int stopflg = 0;
pthread_mutex_lock(&mutex);
while (1) {
curidx = queue_notempty();
// queue has an entry -- process it
if (curidx >= 0) {
rol = &roller[curidx];
printf("next_alarm_read() reading #%d\n", curidx);
*res = *rol;
//prev_time = rol->timestamp;
// if writer is waiting/blocking, wake it up because we just
// freed up a queue slot
if (need_notfull) {
DBG("reader signal notfull");
need_notfull = 0;
pthread_cond_signal(&cv_notfull);
}
break;
}
// stop when master has enqueued everything
stopflg = stopall;
if (stopflg)
break;
// queue is empty -- we must wait for writer to add something
DBG("reader need_notempty");
need_notempty = 1;
pthread_cond_wait(&cv_notempty,&mutex);
}
pthread_mutex_unlock(&mutex);
return stopflg;
}
UPDATE:
I don't understand the do while(0); "loops" in the two Q functions, can you elaboratea little, please?
The do while(0) is a technique that I use a lot to replace if/else ladder logic. I didn't invent it [it's discussed in some style guides, notably, "Code Complete"], but a lot of people that I've shown it to seem to like it. See my answer: About the exclusiveness of the cases of an if block for a better explanation.
And I guessx what my initrial post didn't include is: the master should be able to enqueue things on an ongoing basis, there's no stopall and the reader should start reading as soon as something is available.
Actually, I did realize that and the code I posted allows for that.
You may want to issue the pthread_create before enqueuing any messages to prevent the potential deadlock I mentioned in the code comments.
A fix for this would be to remove stopall, the pthread_cond-signal() (from main) is already done inside alarm_add() so this should work fine.
The stopall is not to synchronize against overflow/underflow. It is merely if the writer (main thread) wants the receiver/thread to finish up and stop cleanly. It's more like a way to send an "EOF" condition to the reader.
If your application is to run "forever", you can remove the stopall.
Or, a cleaner way to signal "EOF": the main thread could enqueue a special "stop" message (e.g. a message with a timestamp of -1) to tell the receiver that no more messages will be sent ever and we wish to terminate the program.
What I suggest is that you add a "diagnostic mode" to validate your program:
Have the main do the pthread_create and then do:
for (i = 1; i < 10000000; i++) {
alarm_add(LOS_ACT, i, 0);
}
The reader should examine the arg1 values that come in. They should increment as above. If they don't, there is a logic error or race condition.
Here is an updated version of my code with a -D option for a diagnostic/unit test mode. Note that all printing is disabled to allow it to run at extreme speed:
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#define NUM_ALM 5
#define ERROR -1
#define OK 0
int opt_diag;
double tvzero;
//even IDs = alarm active
//odd IDs = alarm clear
enum alarmid {
BFD_ACT = 0x02,
BFD_CLR = 0x03,
LOS_ACT = 0x0C
};
typedef struct alarm_s {
long timestamp;
int alarmid;
int arg1;
int arg2;
} alarm_t;
void alarm_add(int id, int arg1, int arg2);
int next_alarm_read(alarm_t * res);
void *alarm_reader(void *arg);
static alarm_t *roller;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// reader variables
pthread_cond_t cv_notempty; // writer signals when queue not empty
volatile int need_notempty; // reader sets this before waiting
volatile int idxdeq; // reader's queue index
// writer variables
pthread_cond_t cv_notfull; // reader signals when queue not full
volatile int need_notfull; // writer sets this before waiting
volatile int idxenq; // writer's queue index
volatile int stopall;
double
tvgetf(void)
{
struct timespec ts;
double sec;
clock_gettime(CLOCK_REALTIME,&ts);
sec = ts.tv_nsec;
sec /= 1e9;
sec += ts.tv_sec;
sec -= tvzero;
return sec;
}
#define prtf(_fmt...) \
do { \
if (opt_diag) \
break; \
printf(_fmt); \
} while (0)
#define DBG(_reason) \
dbg(_reason)
void
dbg(const char *reason)
{
double tvnow;
if (! opt_diag) {
tvnow = tvgetf();
printf("[%.9f] %s\n",tvnow,reason);
}
}
int
main(int argc,char **argv)
{
int i = 0;
char *cp;
pthread_t reader;
int ret;
--argc;
++argv;
for (; argc > 0; --argc, ++argv) {
cp = *argv;
if (*cp != '-')
break;
switch (cp[1]) {
case 'D':
cp += 2;
opt_diag = (*cp != 0) ? atoi(cp) : 10000000;
break;
}
}
tvzero = tvgetf();
roller = calloc(NUM_ALM, sizeof(alarm_t));
printf("allocated memory: %lukB\n", (sizeof(alarm_t) * NUM_ALM) / 1024);
// NOTE: queuing more than a full queue here will cause writer to block
// forever because reader is not yet started
if (! opt_diag) {
for (i = 1; i < NUM_ALM; i++) {
alarm_add(LOS_ACT, i, 0);
}
}
ret = pthread_create(&reader, NULL, alarm_reader, NULL);
if (ret) {
printf("Error - pthread_create() return code: %d\n", ret);
return ERROR;
}
#if 0
sleep(1);
#endif
if (opt_diag) {
for (i = 1; i < opt_diag; i++) {
alarm_add(LOS_ACT, i, 0);
}
}
else {
alarm_add(BFD_ACT, 8, 0);
alarm_add(BFD_ACT, 8, 0);
alarm_add(BFD_ACT, 8, 0);
alarm_add(BFD_ACT, 8, 0);
alarm_add(BFD_CLR, 8, 0);
alarm_add(BFD_CLR, 8, 0);
alarm_add(BFD_CLR, 8, 0);
alarm_add(BFD_CLR, 8, 0);
alarm_add(BFD_ACT, 8, 0);
}
// tell reader that all items are queued and it should stop when it
// processes the final item
pthread_mutex_lock(&mutex);
stopall = 1;
if (need_notempty)
pthread_cond_signal(&cv_notempty);
pthread_mutex_unlock(&mutex);
pthread_join(reader, NULL);
return 0;
}
// RETURNS: queue index to process (-1=empty)
int
queue_notempty(void)
{
int curidx;
do {
curidx = idxdeq;
// queue is empty
if (curidx == idxenq) {
curidx = -1;
break;
}
// advance dequeue index
idxdeq += 1;
idxdeq %= NUM_ALM;
} while (0);
return curidx;
}
// RETURNS: queue index to use (-1=full)
int
queue_notfull(void)
{
int nxtidx;
int curidx;
do {
// get current index
curidx = idxenq;
// advance to next slot (wrapping if necessary)
nxtidx = curidx;
nxtidx += 1;
nxtidx %= NUM_ALM;
// queue is full
if (nxtidx == idxdeq) {
curidx = -1;
break;
}
// store back adjusted index
idxenq = nxtidx;
} while (0);
return curidx;
}
void *
alarm_reader(void *arg)
{
alarm_t dat = { 0 };
static int expval = 1;
while (1) {
if (next_alarm_read(&dat))
break;
if (opt_diag) {
if (dat.arg1 != expval) {
printf("expected: %d got %d\n",expval,dat.arg1);
exit(1);
}
++expval;
}
prtf("read alarm id %d, arg1 %d,arg2 %d\n",
dat.alarmid, dat.arg1, dat.arg2);
}
printf("alarm_reader exit!\n");
return (void *) 0;
}
void
alarm_add(int id, int arg1, int arg2)
{
int curidx;
alarm_t *rol;
pthread_mutex_lock(&mutex);
while (1) {
curidx = queue_notfull();
// have an open slot -- store item into it
if (curidx >= 0) {
rol = &roller[curidx];
rol->timestamp = time(NULL);
rol->alarmid = id;
rol->arg1 = arg1;
rol->arg2 = arg2;
prtf("added id %d, arg1 %d, arg2 %d #%d\n",
rol->alarmid, rol->arg1, rol->arg2, curidx);
// unblock reader if necessary
if (need_notempty) {
DBG("writer signal notempty");
need_notempty = 0;
pthread_cond_signal(&cv_notempty);
}
break;
}
// queue is full -- wait for reader to free up some space
DBG("writer need_notfull");
need_notfull = 1;
pthread_cond_wait(&cv_notfull,&mutex);
DBG("writer wakeup");
}
pthread_mutex_unlock(&mutex);
}
// RETURNS: 1=stop, 0=normal
int
next_alarm_read(alarm_t *res)
{
//static long prev_time = 0;
int curidx;
alarm_t *rol;
int stopflg = 0;
pthread_mutex_lock(&mutex);
while (1) {
curidx = queue_notempty();
// queue has an entry -- process it
if (curidx >= 0) {
rol = &roller[curidx];
prtf("next_alarm_read() reading #%d\n", curidx);
*res = *rol;
//prev_time = rol->timestamp;
// if writer is waiting/blocking, wake it up because we just
// freed up a queue slot
if (need_notfull) {
DBG("reader signal notfull");
need_notfull = 0;
pthread_cond_signal(&cv_notfull);
}
break;
}
// stop when master has enqueued everything
stopflg = stopall;
if (stopflg)
break;
// queue is empty -- we must wait for writer to add something
DBG("reader need_notempty");
need_notempty = 1;
pthread_cond_wait(&cv_notempty,&mutex);
}
pthread_mutex_unlock(&mutex);
return stopflg;
}
Here is a version of your original code with the diagnostic option added:
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
int opt_diag;
#define NUM_ALM 5
#define ERROR -1
#define OK 0
//even IDs = alarm active
//odd IDs = alarm clear
enum alarmid {
BFD_ACT = 0x02,
BFD_CLR = 0x03,
LOS_ACT = 0x0C
};
typedef struct alarm_s {
long timestamp;
int alarmid;
int arg1;
int arg2;
} alarm_t;
int alarm_add(int id, int arg1, int arg2);
int next_alarm_read(alarm_t * res);
void *alarm_reader(void *arg);
static alarm_t *roller;
pthread_cond_t cv;
pthread_mutex_t mutex;
#define prtf(_fmt...) \
do { \
if (opt_diag) \
break; \
printf(_fmt); \
} while (0)
int
main(int argc,char **argv)
{
int i = 0;
char *cp;
pthread_t reader;
int ret;
--argc;
++argv;
for (; argc > 0; --argc, ++argv) {
cp = *argv;
if (*cp != '-')
break;
switch (cp[1]) {
case 'D':
cp += 2;
opt_diag = (*cp != 0) ? atoi(cp) : 10000000;
break;
}
}
roller = calloc(NUM_ALM, sizeof(alarm_t));
printf("allocated memory: %lukB\n", (sizeof(alarm_t) * NUM_ALM) / 1024);
if (! opt_diag) {
for (i = 1; i < NUM_ALM; i++) {
alarm_add(LOS_ACT, i, 0);
}
}
ret = pthread_create(&reader, NULL, alarm_reader, NULL);
if (ret) {
printf("Error - pthread_create() return code: %d\n", ret);
return ERROR;
}
if (opt_diag) {
for (i = 1; i < opt_diag; i++) {
alarm_add(LOS_ACT, i, 0);
}
}
else {
sleep(1);
alarm_add(BFD_ACT, 8, 0);
alarm_add(BFD_ACT, 8, 0);
alarm_add(BFD_ACT, 8, 0);
alarm_add(BFD_ACT, 8, 0);
alarm_add(BFD_CLR, 8, 0);
alarm_add(BFD_CLR, 8, 0);
alarm_add(BFD_CLR, 8, 0);
alarm_add(BFD_CLR, 8, 0);
alarm_add(BFD_ACT, 8, 0);
}
pthread_join(reader, NULL);
}
void *
alarm_reader(void *arg)
{
static alarm_t dat = { 0 };
int expval = 1;
int err = 0;
while (err <= 2) {
if (next_alarm_read(&dat) == OK) {
prtf("read alarm id %d, arg1 %d,arg2 %d\n", dat.alarmid, dat.arg1, dat.arg2);
if (opt_diag) {
if (dat.arg1 != expval) {
printf("expected: %d got %d\n",expval,dat.arg1);
exit(1);
}
++expval;
}
}
else {
prtf("alarm_reader() next_alarm_read() returned ERROR, wait\n");
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cv, &mutex);
pthread_mutex_unlock(&mutex);
err++;
}
}
printf("alarm_reader exit!\n");
return (void *) 0;
}
int
alarm_add(int id, int arg1, int arg2)
{
static int i = 0;
alarm_t dat = { 0 };
if (i < NUM_ALM) {
dat.timestamp = time(NULL);
dat.alarmid = id;
dat.arg1 = arg1;
dat.arg2 = arg2;
if (&roller[i]) {
memcpy(&roller[i], &dat, sizeof(alarm_t));
if (i + 1 < NUM_ALM)
roller[i + 1].alarmid = 0;
else
roller[0].alarmid = 0;
pthread_cond_signal(&cv);
prtf("added id %d, arg1 %d, arg2 %d #%d\n", roller[i].alarmid, roller[i].arg1, roller[i].arg2, i);
i++;
}
}
else {
i = 0;
}
return 0;
}
int
next_alarm_read(alarm_t * res)
{
static int i = 0;
//static long prev_time = 0;
if (!res)
return ERROR;
if (i < NUM_ALM) {
if (roller[i].alarmid != 0) {
prtf("next_alarm_read() reading #%d\n", i);
res->timestamp = roller[i].timestamp;
res->alarmid = roller[i].alarmid;
res->arg1 = roller[i].arg1;
res->arg2 = roller[i].arg2;
//prev_time = roller[i].timestamp;
i++;
}
else {
prtf("next_alarm_read() #%d is %d,return ERROR\n", i, roller[i].alarmid);
return ERROR;
}
}
else {
i = 0;
}
return OK;
}

Related

c program loop in fork doesn't stop

I am simulating F1 training. I am using fork() and shared memory between processes.
I am generating a random time for the cars that need to run for 5,400,000.
I fork() for each car.
the sons must simulate the generation of times.
the father takes care of the display, he also makes a system ("clear") with a sleep (1) to slow down the display.
but the program does not stop despite the 5,400,000 limit.
The program lasts more than 20min without stopping!
I would like it to generate the display a number of times and stop!
Thank you.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/wait.h>
#define NUMBER_OF_CARS 20
#define MIN 25000 // time generator
#define MAX 40000
int numeroVoiture[NUMBER_OF_CARS] = {44, 77, 11, 33, 3, 4, 5, 18, 14, 31, 16, 55, 10, 22, 7, 99, 9, 47, 6, 63};
typedef struct {
unsigned int id;
unsigned int s1;
unsigned int s2;
unsigned int s3;
unsigned int best_S1;
unsigned int best_S2;
unsigned int best_S3;
unsigned int tempsTotal;
unsigned int best_Circuit;
unsigned int lap;
unsigned int compteurStand;
unsigned int out;
} voiture;
voiture *shared_memory;
voiture copyTableau[NUMBER_OF_CARS];
int faireDesTours(int i);
unsigned int generateNumber(void);
void afficherTableau(void);
int compare (const void * a, const void * b);
void initVoiture(int i);
void sortLap(void);
int main(void)
{
/***************************************************
* Creating shared memory *
****************************************************/
int segment_id = shmget(IPC_PRIVATE, sizeof(voiture) * NUMBER_OF_CARS, 0666 | IPC_CREAT);
if (segment_id == -1) {
perror("shmget() failed !");
exit(EXIT_FAILURE);
}
shared_memory = shmat(segment_id, NULL, 0);
if (shared_memory == (void *) (-1)) {
perror("shmat() failed !");
exit(EXIT_FAILURE);
}
/**********************************************************
* Creation of child / cars *
**********************************************************/
for (int i = 0; i < NUMBER_OF_CARS; ++i)
{
/******** problem fork *********/
pid_t pid = fork();
if (pid == -1) {
perror("fork failed !");
exit(EXIT_FAILURE);
}
/******** child *********/
if(pid == 0) {
shared_memory[i].id = numeroVoiture[i]; //each car has a number
faireDesTours(i); //5400000
}
/******** father *********/
else {
wait(NULL);
system("clear");
// copy of array
memcpy( copyTableau, shared_memory, sizeof(copyTableau) );
//trier Tableau;
qsort( copyTableau, NUMBER_OF_CARS, sizeof(voiture), compare );
sortLap();
afficherTableau();
sleep(1);
}
}
/******** Detach memory segments *********/
shmdt(shared_memory);
/******** Delete shared memory *********/
shmctl(segment_id, IPC_RMID, NULL);
exit(EXIT_SUCCESS);
}
unsigned int tempsMaxCircuit = 5400000;
int faireDesTours( int i ) {
initVoiture(i);
unsigned int tour_complet;
while (shared_memory[i].tempsTotal <= tempsMaxCircuit) //no exceeded time
{
tour_complet = 0;
srand(time(NULL) + getpid());
/* **** S1 **** */
shared_memory[i].s1 = generateNumber();
if (shared_memory[i].s1 < shared_memory[i].best_S1) {
shared_memory[i].best_S1 = shared_memory[i].s1;
}
shared_memory[i].tempsTotal += shared_memory[i].s1;
tour_complet += shared_memory[i].s1;
/* *************************************** */
if (shared_memory[i].tempsTotal >= tempsMaxCircuit)
{
break;
}
/* **** S2 **** */
shared_memory[i].s2 = generateNumber();
if (shared_memory[i].s2 < shared_memory[i].best_S2) {
shared_memory[i].best_S2 = shared_memory[i].s2;
}
shared_memory[i].tempsTotal += shared_memory[i].s2;
tour_complet += shared_memory[i].s2;
/* *************************************** */
if (shared_memory[i].tempsTotal >= tempsMaxCircuit)
{
break;
}
/* **** S3 **** */
shared_memory[i].s3 = generateNumber();
if (shared_memory[i].s3 < shared_memory[i].best_S3) {
shared_memory[i].best_S3 = shared_memory[i].s3;
}
shared_memory[i].tempsTotal += shared_memory[i].s3;
tour_complet += shared_memory[i].s3;
/* *************************************** */
/* **** Best Time Circuit **** */
if (tour_complet < shared_memory[i].best_Circuit) {
shared_memory[i].best_Circuit = tour_complet;
shared_memory[i].best_Circuit = tour_complet;
}
/* *************************************** */
}
return 0;
}
unsigned int generateNumber(void)
{
return rand()%(MAX-MIN+1)+MIN;
}
void afficherTableau(void) { // Display
printf("\n\tBest times per complete lap\n");
printf(" ===================================================================================\n");
printf(" | ID | s1 | s2 | s3 | Tour | LAP |\n");
printf(" |==================================================================================\n");
for (int i = 0; i < NUMBER_OF_CARS; i++){
printf(" | %2d | %5d | %5d | %5d | %6d | %5d |\n", \
copyTableau[i].id, \
copyTableau[i].s1, copyTableau[i].s2, copyTableau[i].s3, \
copyTableau[i].best_Circuit,\
copyTableau[i].lap);
}
printf(" ===================================================================================\n\n");
}
// function sort
int compare(const void * a, const void * b)
{
voiture *voitureA = (voiture *)a;
voiture *voitureB = (voiture *)b;
return ( voitureA->best_Circuit - voitureB->best_Circuit );
}
// init each structure value of car
void initVoiture(int i) {
shared_memory[i].s1 = 0;
shared_memory[i].s2 = 0;
shared_memory[i].s3 = 0;
shared_memory[i].best_S1 = MAX;
shared_memory[i].best_S2 = MAX;
shared_memory[i].best_S3 = MAX;
shared_memory[i].best_Circuit = 3 * MAX;
shared_memory[i].tempsTotal = 0;
shared_memory[i].lap = 0;
shared_memory[i].compteurStand = 0;
shared_memory[i].out = false;
}
void sortLap(void) {
unsigned int difference;
for (int i = 1; i < NUMBER_OF_CARS; i++)
{
difference = ( copyTableau[i].best_Circuit - copyTableau[i - 1].best_Circuit );
copyTableau[i].lap = difference;
}
}
At the core, you have
for (int i = 0; i < NUMBER_OF_CARS; ++i)
{
/******** problem fork *********/
pid_t pid = fork();
if (pid == -1) {
perror("fork failed !");
exit(EXIT_FAILURE);
}
/******** child *********/
if(pid == 0) {
...
}
/******** father *********/
else {
wait(NULL);
...
}
}
You're missing a call to exit in the child. Without this, each child loops around and starts creating children of its own. It will eventually end, but not after creating a huge number of processes unintentionally.
By the way, it's kind of weird to create a child if the first thing the parent does is wait for the child to end.

Multithreading : busy waiting in producer-consumer problem

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <wait.h>
#include <pthread.h>
int item_to_produce, curr_buf_size;
int total_items, max_buf_size, num_workers, num_masters;
int consumed_items;
int *buffer;
pthread_mutex_t mutex;
pthread_cond_t has_data;
pthread_cond_t has_space;
void print_produced(int num, int master) {
printf("Produced %d by master %d\n", num, master);
}
void print_consumed(int num, int worker) {
printf("Consumed %d by worker %d\n", num, worker);
}
//consume items in buffer
void *consume_requests_loop(void *data)
{
int thread_id = *((int *)data);
while(1)
{
pthread_mutex_lock(&mutex); // mutex lock for consume
if(consumed_items == total_items) {
pthread_mutex_unlock(&mutex);
break;
}
while(curr_buf_size == 0) {
pthread_cond_wait(&has_data, &mutex);
}
print_consumed(buffer[(curr_buf_size--)-1], thread_id);
consumed_items++;
pthread_cond_signal(&has_space);
pthread_mutex_unlock(&mutex);
}
return 0;
}
//produce items and place in buffer
//modify code below to synchronize correctly
void *generate_requests_loop(void *data)
{
int thread_id = *((int *)data);
while(1) {
pthread_mutex_lock(&mutex); // mutex lock for consume
//all of items are produced
//master threads need to join
if(item_to_produce == total_items) {
pthread_mutex_unlock(&mutex);
break;
}
//there is no item to read
while (curr_buf_size == max_buf_size) {
pthread_cond_wait(&has_space, &mutex);
}
buffer[curr_buf_size++] = item_to_produce;
print_produced(item_to_produce, thread_id);
item_to_produce++;
pthread_cond_signal(&has_data);
pthread_mutex_unlock(&mutex); // mutex_produce unlock
}
return 0;
}
//write function to be run by worker threads
//ensure that the workers call the function print_consumed when they consume an item
int main(int argc, char *argv[])
{
int *master_thread_id; // array of master_thread_id
int *worker_thread_id; // array of worker_thread_id
pthread_t *master_thread; // array of master_thread
pthread_t *worker_thread; // array of worker_thread
item_to_produce = 0; // item will be produced by master_thread at next time
curr_buf_size = 0; // index of item will be saved in
consumed_items = 0;
int i;
if (argc < 5) {
printf("./master-worker #total_items #max_buf_size #num_workers #masters e.g. ./exe 10000 1000 4 3\n");
exit(1);
}
else {
num_masters = atoi(argv[4]);
num_workers = atoi(argv[3]);
total_items = atoi(argv[1]);
max_buf_size = atoi(argv[2]);
}
buffer = (int *)malloc (sizeof(int) * max_buf_size);
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&has_space, NULL);
pthread_cond_init(&has_data, NULL);
//create master producer threads
master_thread_id = (int *)malloc(sizeof(int) * num_masters);
master_thread = (pthread_t *)malloc(sizeof(pthread_t) * num_masters);
for (i = 0; i < num_masters; i++)
master_thread_id[i] = i;
for (i = 0; i < num_masters; i++)
pthread_create(&master_thread[i], NULL, generate_requests_loop, (void *)&master_thread_id[i]);
//create worker consumer threads
worker_thread_id = (int *)malloc(sizeof(int) * num_workers);
worker_thread = (pthread_t *)malloc(sizeof(pthread_t) * num_workers);
for (i = 0; i < num_workers; i++)
worker_thread_id[i] = i;
for (i = 0 ; i < num_workers; i++)
pthread_create(&worker_thread[i], NULL, consume_requests_loop, (void *)&worker_thread_id[i]);
//wait for all threads to complete
for (i = 0; i < num_masters; i++)
{
pthread_join(master_thread[i], NULL);
printf("master %d joined\n", i);
}
for (i = 0; i < num_workers; i++)
{
pthread_join(worker_thread[i], NULL);
printf("worker %d joined\n", i);
}
/*----Deallocating Buffers---------------------*/
free(buffer);
free(master_thread_id);
free(master_thread);
free(worker_thread_id);
free(worker_thread);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&has_data);
pthread_cond_destroy(&has_space);
return 0;
}
This code produces a number in the range of given numbers through the argument and consumes it.
But producer produces a number outside the range and doesn't join if it matches the condition. The consumer is too.
e.g when I give range of number like 0~39(total_item = 500), buff size 30(max_buf_size), num_workers 5, num_master 3, it doesn't produce and consume number only 0~39.
It produces and consumes numbers over 40.
In that way the thread is in a loop. To put the thread in sleep you can use, for example, the condition variables. (You can read this for more info https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_wait.html)

Multithreaded Fibonacci Pair Program

I'm trying to write a program which creates two threads: a "front-end" and "back-end" thread. I want to create a "back-end" thread to iterate and compute pairs of terms from the fibonacci sequence and put them in an array, and a "front-end" thread that will print out the pairs of the array at each iteration.
"Front-End" Thread - For displaying result of "Back-End" thread operations in each iterations
"Back-End" Thread - For calculating and setting an array
ie. [5, 8], and after an iteration it will contain [13, 21]
I'm struggling to implement the Fibonacci sequence part in a thread and I've made the following progress:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
int fib;
void *front_end(void *ptr);
void *back_end(void *ptr);
int main() {
pthread_t thread1, thread2;
int arr[2] = {5,8};
const int *ptrtoarr;
ptrtoarr=arr;
int create1, create2;
int *s=(int *)(ptrtoarr);
printf("%d \n", *s);
ptrtoarr++;
s = (int *)(ptrtoarr);
printf("%d \n", *s);
ptrtoarr--;
create1 = pthread_create(&thread1, NULL, back_end, &arr);
if(create1) {
fprintf(stderr,"Error - pthread_create() return code: %d\n",create1);
exit(EXIT_FAILURE);
}
pthread_join(thread1, NULL);
//pthread_join(thread2, NULL);
}
// front-end thread to be callback for each back-end iteration
void *front_end(void *ptr) {
int *sum = ptr;
int i, upper = atoi(ptr);
if (upper > 0) {
for (i=0; i<upper; i++){
//Print the fib pairs
}
}
pthread_exit(0);
}
void *back_end(void *ptr) {
int i, upper = atoi(ptr);
fib=1;
if(upper > 0) {
int pre1 = 0;
int current;
//calc fib numbers.....
if(fib == 1){
printf("")
}
}
}
Can someone guide me through how I might approach this?
Your skeleton needs work.
Assuming the following:
unsigned n = ...; // How many to generate.
unsigned n_ready = 2; // How many are ready to print.
unsigned *fibs = malloc(sizeof(unsigned)*n);
fibs[0] = 0;
fibs[1] = 1;
At the core of your back end worker, you will have
for (unsigned i=2; i<n; ++i) {
fibs[i] = fibs[i-2] + fibs[i-1];
n_ready = i+1;
}
At the core of your frontend worker, you will have
for (unsigned i=0; i<n; ++i) {
while (i >= n_ready)
/* Nothing */;
printf("%u\n", fibs[i]);
}
Problem #1
You get into problems if a thread tries to read a variable when another is writing to it. Two or more threads reading the same variable at the same time is ok.
The variables used by both threads are n, the elements of fib[] and n_ready.
n:Not changed by either thread, so we don't need to control access to it.
fib[i] for i >= n_ready:Only accessed by the back end worker, so we don't need to control access to these.
fib[i] for i < n_ready:Only accessed by the frontend worker, so we don't need to control access to these.
n_ready:The back end worker could set n_ready at any time, and the frontend work could try to read n_ready at any time, so we do need to control access to n_ready.
Mutex are usually used to ensure that only one thread is accessing a resource (e.g. a variable, group of variables, file handle, etc) at a time.
Our back end worker becomes
for (unsigned i=2; i<n; ++i) {
// The mutex only protects n_ready
// --nothing else is going to touch fib[i-2] or fib[i-1] or fib[i]--
// so we don't need to obtain a lock yet.
fibs[i] = fibs[i-2] + fibs[i-1];
// We need to access n_ready.
pthread_mutex_lock(&mutex);
n_ready = i+1;
pthread_mutex_unlock(&mutex);
}
Our frontend worker becomes
for (unsigned i=0; i<n; ++i) {
// We need to access n_ready.
pthread_mutex_lock(&mutex);
while (i >= n_ready) {
// Allow other thread to gain the lock.
pthread_mutex_unlock(&mutex);
// We need to access n_ready.
pthread_mutex_lock(&mutex);
}
// The mutex only protects n_ready
// --nothing is going to change fib[i]--
// so we can release it now rather than later.
pthread_mutex_unlock(&mutex);
printf("%u\n", fibs[i]);
}
Problem #2
You have a busy loop. In general, this is bad because it means your thread is using 100% doing nothing by waiting. (In this particular case, since i >= n_ready is probably already true, this would actually be a good strategy. But let's ignore that.) A thread can sleep until signaled by another thread using condition vars.
Our back end worker becomes
for (unsigned i=2; i<n; ++i) {
// The mutex only protects n_ready
// --nothing else is going to touch fib[i-2] or fib[i-1] or fib[i]--
// so we don't need to obtain a lock yet.
fibs[i] = fibs[i-2] + fibs[i-1];
// We need to access n_ready.
pthread_mutex_lock(&mutex);
n_ready = i+1;
// Wake up the other thread if it's blocked.
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
Our frontend worker becomes
for (unsigned i=0; i<n; ++i) {
// We need to access n_ready.
pthread_mutex_lock(&mutex);
while (i >= n_ready)
pthread_cond_wait(&cond, &mutex);
// The mutex only protects n_ready
// --nothing is going to change fib[i]--
// so we can release it now rather than later.
pthread_mutex_unlock(&mutex);
printf("%u\n", fibs[i]);
}
Always call pthread_cond_wait on a locked mutex. It will unlock the mutex when it's called, and it will lock it before returning. This allows the other thread to obtain the mutex in order to change n_ready.
Complete code:
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define UNUSED(x) (void)(x)
// To control access to n_ready.
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static unsigned n_ready = 0; // How many are ready to print.
static unsigned n; // How many to generate.
static unsigned *fibs = NULL;
static void *back_worker(void *unused) {
UNUSED(unused);
fibs[0] = 0;
fibs[1] = 1;
// We need to access n_ready.
pthread_mutex_lock(&mutex);
n_ready = 2;
// Wake up the other thread if it's blocked.
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
for (unsigned i=2; i<n; ++i) {
// The mutex only protects n_ready
// --nothing is going to touch fib[i]--
// so we don't need to obtain a lock yet.
fibs[i] = fibs[i-2] + fibs[i-1];
// We need to access n_ready.
pthread_mutex_lock(&mutex);
n_ready = i+1;
// Wake up the other thread if it's blocked.
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
static void *front_worker(void *unused) {
UNUSED(unused);
for (unsigned i=0; i<n; ++i) {
// We need to access n_ready.
pthread_mutex_lock(&mutex);
while (i >= n_ready)
pthread_cond_wait(&cond, &mutex);
// The mutex only protects n_ready
// --nothing is going to change fib[i]--
// so we can release it now rather than later.
pthread_mutex_unlock(&mutex);
printf("%u\n", fibs[i]);
}
return NULL;
}
int main(void) {
n = 20; // How many to generate.
fibs = malloc(sizeof(unsigned) * n);
pthread_t back_thread;
if (errno = pthread_create(&back_thread, NULL, back_worker, NULL)) {
perror(NULL);
exit(1);
}
pthread_t front_thread;
if (errno = pthread_create(&front_thread, NULL, front_worker, NULL)) {
perror(NULL);
exit(1);
}
pthread_join(back_thread, NULL);
pthread_join(front_thread, NULL);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
free(fibs);
return 0;
}
Output:
$ gcc -Wall -Wextra -pedantic a.c -o a -lpthread && a
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
Suggestion for an exercise to apply the above
Create a pool of workers that print out the numbers placed into a queue. The output doesn't need to be in order.
The worker function is already written for you. You may not change the main or worker functions. I've even created the queue for you. You simply have to make it thread safe by modifying Queue_enqueue, Queue_dequeue and Queue_done functions. These are the only functions you may change.
#include <errno.h>
#include <inttypes.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define NUM_WORKERS 4
#define QUEUE_SIZE 10
#define NUM_ITEMS 40
typedef struct {
pthread_mutex_t mutex;
pthread_cond_t cond;
int done;
int empty;
int full;
size_t max;
size_t next_insert;
size_t next_read;
unsigned *buf;
} Queue;
static void Queue_init(Queue* q, size_t max) {
pthread_mutex_init(&(q->mutex), NULL);
pthread_cond_init(&(q->cond), NULL);
q->done = 0;
q->empty = 1;
q->full = 0;
q->max = max;
q->next_insert = 0;
q->next_read = 0;
q->buf = malloc(sizeof(unsigned)*max);
}
static void Queue_destroy(Queue *q) {
free(q->buf);
pthread_cond_destroy(&(q->cond));
pthread_mutex_destroy(&(q->mutex));
}
static void Queue_done(Queue *q) {
q->done = 1;
}
// Returns the oldest item from the queue (via a parameter) and returns 1.
// If the queue is empty and done, returns 0.
// If the queue is empty and not done, waits until that changes.
static int Queue_dequeue(Queue *q, unsigned *i) {
while (q->empty && !q->done) {
}
if (q->empty) {
// We are completely done.
return 0;
} else {
*i = q->buf[ q->next_read ];
q->next_read = ( q->next_read + 1 ) % q->max;
q->empty = q->next_read == q->next_insert;
q->full = 0;
return 1;
}
}
// Adds the argument to the queue.
// If the queue is full, waits until that changes.
static void Queue_enqueue(Queue *q, unsigned i) {
while (q->full && !q->done) {
}
if (q->done) {
fprintf(stderr, "Error: Attempted to add item to \"done\" queue.\n");
return;
}
q->buf[q->next_insert] = i;
q->next_insert = ( q->next_insert + 1 ) % q->max;
q->empty = 0;
q->full = q->next_insert == q->next_read;
}
static int msleep(long msec) {
struct timespec ts;
int res;
if (msec < 0) {
errno = EINVAL;
return -1;
}
ts.tv_sec = msec / 1000;
ts.tv_nsec = (msec % 1000) * 1000000;
do {
res = nanosleep(&ts, &ts);
} while (res && errno == EINTR);
return res;
}
// Protects access to stdout.
static pthread_mutex_t stdout_mutex;
static Queue q;
static void *worker(void *worker_id_) {
uintptr_t worker_id = (uintptr_t)worker_id_;
unsigned int seed = worker_id; // Whatever.
unsigned i;
while (Queue_dequeue(&q, &i)) {
pthread_mutex_lock(&stdout_mutex);
printf("[%" PRIuPTR "] Dequeued %u\n", worker_id, i);
pthread_mutex_unlock(&stdout_mutex);
// msleep( rand_r(&seed) % 1000 + 1000 ); // Simulate a 1 to 2s load.
pthread_mutex_lock(&stdout_mutex);
printf("[%" PRIuPTR "] Finished processing %u\n", worker_id, i);
pthread_mutex_unlock(&stdout_mutex);
}
return NULL;
}
int main(void) {
Queue_init(&q, QUEUE_SIZE);
pthread_t workers[NUM_WORKERS];
for (uintptr_t i=0; i<NUM_WORKERS; ++i) {
if (errno = pthread_create(&(workers[i]), NULL, worker, (void*)i)) {
perror(NULL);
exit(1);
}
}
for (unsigned i=0; i<NUM_ITEMS; ++i) {
pthread_mutex_lock(&stdout_mutex);
printf("[x] Enqueuing %u...\n", i);
pthread_mutex_unlock(&stdout_mutex);
Queue_enqueue(&q, i);
pthread_mutex_lock(&stdout_mutex);
printf("[x] Enqueued %u.\n", i);
pthread_mutex_unlock(&stdout_mutex);
}
Queue_done(&q);
pthread_mutex_lock(&stdout_mutex);
printf("[x] Called done.\n");
pthread_mutex_unlock(&stdout_mutex);
for (unsigned i=0; i<NUM_WORKERS; ++i)
pthread_join(workers[i], NULL);
Queue_destroy(&q);
pthread_mutex_destroy(&stdout_mutex);
return 0;
}
If you have questions about this, feel free to post a link to the question as a comment to this answer.
Solution to suggested excercise:
static void Queue_done(Queue *q) {
pthread_mutex_lock(&(q->mutex));
q->done = 1;
pthread_cond_signal(&(q->cond));
pthread_mutex_unlock(&(q->mutex));
}
// Returns the oldest item from the queue (via a parameter) and returns 1.
// If the queue is empty and done, returns 0.
// If the queue is empty and not done, waits until that changes.
static int Queue_dequeue(Queue *q, unsigned *i) {
pthread_mutex_lock(&(q->mutex));
while (q->empty && !q->done)
pthread_cond_wait(&(q->cond), &(q->mutex));
int dequeued;
if (q->empty) {
// We are completely done.
dequeued = 0;
} else {
*i = q->buf[ q->next_read ];
q->next_read = ( q->next_read + 1 ) % q->max;
q->empty = q->next_read == q->next_insert;
q->full = 0;
dequeued = 1;
}
pthread_cond_signal(&(q->cond));
pthread_mutex_unlock(&(q->mutex));
return dequeued;
}
// Adds the argument to the queue.
// If the queue is full, waits until that changes.
static void Queue_enqueue(Queue *q, unsigned i) {
pthread_mutex_lock(&(q->mutex));
while (q->full && !q->done)
pthread_cond_wait(&(q->cond), &(q->mutex));
if (q->done) {
fprintf(stderr, "Error: Attempted to add item to \"done\" queue.\n");
} else {
q->buf[q->next_insert] = i;
q->next_insert = ( q->next_insert + 1 ) % q->max;
q->empty = 0;
q->full = q->next_insert == q->next_read;
}
pthread_cond_signal(&(q->cond));
pthread_mutex_unlock(&(q->mutex));
}

C: Using functions from a separate file

Trying to use a bounded buffer from a separate file that I've coded and it seems like that's where the code goes all crazy. Fairly new to C, and I was wondering if I am using the buffer the right way. The concept of instantiation isn't here, so if I just call one of the functions such as bbuff_blocking_insert will the array get initialized? How do I make the appropriate calls in order to get this working?
candy.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include "bbuff.h"
#include <stdbool.h>
#include <time.h>
_Bool stop_thread = false;
typedef struct {
int source_thread;
double time_stamp_in_ms;
} candy_t;
double current_time_in_ms (void) {
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
return now.tv_sec * 1000.0 + now.tv_nsec/1000000.0;
}
void* createCandy(void* arg) {
int r;
int factoryNumber = *(int*)arg;
while(!stop_thread) {
r = rand() % 4;
printf("Random Number: %d\n", r);
printf("\tFactory %d ship candy & wait %ds\n", factoryNumber, r);
candy_t *candy = (candy_t*)malloc(sizeof(candy_t));
candy->source_thread = factoryNumber;
candy->time_stamp_in_ms = current_time_in_ms();
bbuff_blocking_insert((void *)candy);
sleep(r);
}
printf("Candy-factory %d done\n", factoryNumber);
return 0;
}
void* extractCandy(void* arg) {
int r;
candy_t* candy;
while(true) {
candy = (candy_t*)bbuff_blocking_extract();
printf("Candy Source Thread: %d\n", candy->source_thread);
r = rand() % 2;
sleep(r);
}
return 0;
}
int main(int argc, char* argv[]) {
//Extract Arguments
if (argc <= 1) {
printf("Insufficient Arguments\n");
exit(-1);
}
int NO_FACTORIES = atoi(argv[1]);
int NO_KIDS = atoi(argv[2]);
int NO_SECONDS = atoi(argv[3]);
bbuff_init();
//Spawn Factory Threads
pthread_t ftids[NO_FACTORIES];
int factoryNumber[NO_FACTORIES];
for (int i = 0; i < NO_FACTORIES; i++) {
factoryNumber[i] = i;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&ftids[i], &attr, createCandy, &factoryNumber[i]);
}
//Spawn Kid Threads
pthread_t ktids [NO_KIDS];
for (int i = 0; i < NO_KIDS; i++) {
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&ktids[i], &attr, extractCandy, NULL);
}
//Wait for Requested Time
for (int i = 0; i < NO_SECONDS; i++) {
sleep(1);
printf("Time %ds\n", i+1);
}
//Stop Factory Threads
stop_thread = true;
for (int i = 0; i < NO_FACTORIES; i++) {
pthread_join(ftids[i], NULL);
}
//Wait until no more candy
while(bbuff_is_data_available()) {
printf("Waiting for all candy to be consumed");
sleep(1);
}
//Stop kid Threads
for (int i = 0; i < NO_KIDS; i++) {
pthread_cancel(ktids[i]);
pthread_join(ktids[i], NULL);
}
//Print Statistics
//Clean up any allocated memory
return 0;
}
bbuff.h
#ifndef BBUFF_H
#define BBUFF_H
#define QUEUE_SIZE 10
void bbuff_init(void);
void bbuff_blocking_insert(void* item);
void* bbuff_blocking_extract(void);
_Bool bbuff_is_data_available(void);
#endif
bbuff.c
#include "bbuff.h"
pthread_mutex_t mutex;
sem_t empty;
sem_t full;
int in = 0;
int out = 0;
int counter = 0;
void* buffer[QUEUE_SIZE];
void bbuff_init(void){
pthread_mutex_init(&mutex, NULL);
sem_init( &empty, 0, QUEUE_SIZE);
sem_init( &full, 0, 0);
}
void bbuff_blocking_insert(void* item) {
sem_wait(&empty);
pthread_mutex_lock(&mutex);
counter++;
buffer[in] = item;
in = (in+1) % QUEUE_SIZE;
pthread_mutex_unlock(&mutex);
sem_post(&full);
}
void* bbuff_blocking_extract(void) {
void* extractedItem;
sem_wait(&full);
pthread_mutex_lock(&mutex);
counter--;
extractedItem = buffer[out];
buffer[out] = NULL;
out = out % QUEUE_SIZE;
pthread_mutex_unlock(&mutex);
sem_post(&empty);
return extractedItem;
}
Output
$ ./candykids 1 1 10
Random Number: 3
Factory 0 ship candy & wait 3s
Candy Source Thread: 0
Time 1s
Time 2s
Random Number: 1
Factory 0 ship candy & wait 1s
Time 3s
Segmentation fault (core dumped)
In bbuff_blocking_extract(),
out = out % QUEUE_SIZE;
Should be:
out = (out+1) % QUEUE_SIZE;

Segmentation fault after swapcontext in alarm handler

Basically what I am trying to do is simulate multithreading on a single thread with context switching. I set up an alarm for every 10 microseconds, and I switch the context from one to another thread. The problem is that about one in 5 runs ends up with a seg fault right after the alarm finishes the swapcontext, at least that is where I traced it with gdb.
Here are my source files
main.c
#include "umt.h"
void f()
{
int x = 10;
printf("starting thread\n");
while(x)
{
printf("thread %d\n", x);
sleep(1);
x--;
}
}
int main()
{
int x = 0, y, z;
umt_init();
y = umt_thread_create(f);
printf("starting main\n");
if(y == 0)
{
printf("Problems with creating thread\n");
return;
}
x = 10;
z = 1;
while(x)
{
printf("main\n");
x--;
}
umt_thread_join(y);
printf("done waiting\n");
return 0;
}
UMT.h
#include <sys/time.h>
#include <stdio.h>
#include <signal.h>
#include <ucontext.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
typedef struct _umt_thread
{
int thread_id;
ucontext_t context;
void (*handler)(void);
int hasFinished;
}umt_thread, *pumt_thread;
void umt_init();
int umt_thread_create(void (*handler)(void));
void umt_thread_join(int thr);
and umt.c
#include "umt.h"
#define MAIN_CONTEXT 0
#define STACK_SIZE 1638400
int currentThread;
char threadpool[15];
pumt_thread threads;
void signal_thread_finish();
void thread_handler()
{
threads[currentThread].handler();
signal_thread_finish();
}
void thread_scheduler();
void signal_thread_finish()
{
threads[currentThread].hasFinished = TRUE;
threadpool[currentThread] = 0;
thread_scheduler();
}
void thread_scheduler()
{
int nextThread = 0, curThread = 0;
int x = 0;
ucontext_t *con1, *con2;
nextThread = currentThread + 1;
while(1)
{
if(nextThread == 15)
nextThread = 0;
if(nextThread == currentThread)
break;
if(threadpool[nextThread] == 1)
break;
nextThread++;
}
if(nextThread == currentThread)
return;
curThread = currentThread;
currentThread = nextThread;
con1 = &(threads[curThread].context);
con2 = &(threads[nextThread].context);
x = swapcontext(con1, con2);
}
void umt_init()
{
ucontext_t context;
struct itimerval mytimer;
int i;
stack_t new_stack;
getcontext(&context);
threads = (pumt_thread)malloc(sizeof(umt_thread) * 15);
threads[MAIN_CONTEXT].thread_id = MAIN_CONTEXT;
threads[MAIN_CONTEXT].context = context;
threadpool[MAIN_CONTEXT] = 1;
for(i = 1;i<15;i++)
{
threadpool[i] = 0;
}
currentThread = 0;
new_stack.ss_sp = (char*)malloc(STACK_SIZE);
new_stack.ss_size = STACK_SIZE;
new_stack.ss_flags = 0;
i = sigaltstack(&new_stack, NULL);
if(i != 0)
{
printf("problems assigning new stack for signaling\n");
}
signal(SIGALRM, thread_scheduler);
mytimer.it_interval.tv_sec = 0;
mytimer.it_interval.tv_usec = 10;
mytimer.it_value.tv_sec = 0;
mytimer.it_value.tv_usec = 5;
setitimer(ITIMER_REAL, &mytimer, 0);
}
int umt_thread_create(void (*handler)(void))
{
ucontext_t context;
int i, pos;
for(i = 1;i<15;i++)
{
if(threadpool[i] == 0)
{
pos = i;
break;
}
}
if(i == 15)
{
printf("No empty space in the threadpool\n");
return -1;
}
if(getcontext(&context) == -1)
{
printf("Problems getting context\n");
return 0;
}
context.uc_link = 0;//&(threads[MAIN_CONTEXT].context);
context.uc_stack.ss_sp = (char*)malloc(STACK_SIZE);
if(context.uc_stack.ss_sp == NULL)
{
printf("Problems with allocating stack\n");
}
context.uc_stack.ss_size = STACK_SIZE;
context.uc_stack.ss_flags = 0;
makecontext(&context, thread_handler, 0);
threads[pos].thread_id = pos;
threads[pos].context = context;
threads[pos].handler = handler;
threads[pos].hasFinished = FALSE;
threadpool[pos] = 1;
printf("Created thread on pos %d\n", pos);
return pos;
}
void umt_thread_join(int tid)
{
while(!threads[tid].hasFinished)
{
}
}
I tried a lot of combinations and tried tracing by instruction but could not arrive to a conclusion or idea as to what might cause this seg fault. Thanks
Few issues I see (some are related to segfault + some other comments)
You scheduler (thread_scheduler) should be in a critical section, e.g. you should block any alarm signals (or ignore them) so that the handing of the threadpool is done in a way that doesn't corrupt it. you can either use sigprocmask or a volatile boolean variable that will silence the alarm (note this is not the same as the user threads mutex, just an internal synchronization to your scheduling logic)
your clock ticks way too fast IMHO, this is in micro seconds, not milliseconds, so 1000 microseconds for tv_usec might make more sense for testing purposes.
small stack sizes might also cause a seg fault but it seems your stack is big enough.
p.s. there is a better way to handle join, you currently waste lot's of CPU cycles on it, why not simply avoid switching to a thread that called join, untill the thread that it's waiting for has terminated?

Resources