I am making a raytracer, im trying to use pthread to divide the rendering. i noticed that isnt helping with the speed because the function pthread_join is to slow, if i use a loop to make the 'await' is way faster and works almost every time fine. But i cant use that because the time of rendering changes with the scene.
Is there a way to check if a thread is finished, on a more efficient way. This is the code.
`
int threats(t_file *c) //this function creates the threads
{
int i;
int err;
pthread_t th[THREADS];
i = 0;
printf("1\n");
c->thread = -1;
mlx_clear_window(c->mlx_ptr, c->win_ptr);
while (i < THREADS)
{
err = pthread_create(&th[i], 0, (void *)paint_scene, (void *)c);
if (err)
return parse_error("Thread Error: CAN NOT CREATE THREAD");
i++;
}
// while (i-- >= 0)
// pthread_join(th[i], 0);
//my await function xd
while (i < 200000000)
i++;
mlx_put_image_to_window(c->mlx_ptr, c->win_ptr, c->img.mlx_img, 0, 0);
c->thread = 0;
return 1;
}
void paint_scene(void *a)
{
int y;
int x;
t_ray ray;
int color;
t_file *c;
c = (t_file *)a;
color = 0;
c->thread++;
y = (c->thread * (c->win_heigth / THREADS));
printf("y:%d,hilo%d\n", y, c->thread);
while (y < (c->thread + 1) * (c->win_heigth / THREADS))
{
x = 0;
while (x < c->win_width)
{
ray = generate_ray(x, y, *c);
color = get_intersections(&ray, c);
if (c->ligth)
color = shading(&ray, color, c);
my_mlx_pixel_put(&c->img, x, y, color);
x++;
}
//ft_printf("\rLoading%d: %d%%", c->thread, y / (c->win_heigth / 100));
y++;
}
pthread_exit(0);
}
`
You have a concurrency problem here in your thread function:
c->thread++;
y = (c->thread * (c->win_heigth / THREADS));
printf("y:%d,hilo%d\n", y, c->thread);
while (y < (c->thread + 1) * (c->win_heigth / THREADS))
{
....
}
c->thread is shared between all threads, and based on likely thread timings and current face of the moon, I can make an educated guess and say that the first thread is calculating the whole image. When starting up, the first thread might see c->thread == -1, but later (if thread startup is faster than the while loop) other thread increase the value until the first thread sees c->thread == THREADS-1
To fix this, each call to create_thread must pass a pointer to a unique parameter object that holds that threads id. So remove the thread member from t_file. It probably serves no purpose there. And create a type of struct that holds the parameters to the thread function:
struct thread_param
{
unsigned int thread;
file_t *c;
}
You use it like this when starting threads:
struct thread_param params[THREADS];
while (i < THREADS)
{
params[i].thread = i;
params[i].c = c;
err = pthread_create(&th[i], 0, (void *)paint_scene, (void *)&(params[i]));
if (err)
return parse_error("Thread Error: CAN NOT CREATE THREAD");
i++;
}
And you access the data in your thread function:
void paint_scene(void *a)
{
struct thread_param *param = (struct thread_param *)a;
unsigned int thread = param->thread;
t_file *c = param->c;
/*
in the rest of the code you remove `c->thread++`
and replace `c->thread` with `thread`
*/
....
}
If you have atomic data types (C11, #ifndef __STDC_NO_ATOMICS__) then implement a global counter and wait until it hits zero (if decreasing) or the amount of threads (if increasing).
e.g.
#include <stdatomic.h>
atomic_int num_jobs;
void* thread_func(void*)
{
//the work to do in the thread function
//before exit decrease counter
--num_jobs;
pthread_exit(0);
}
int main()
{
num_jobs = THREADS; // same as your number of threads
create_threads(THREADS); // number of threads = THREADS
while (num_jobs) { // loop while threads running
//sleep for a while
}
join_threads(); // join threads for cleanup
return 0;
}
Otherwise classic lock mechanics,
e.g.
#include <pthread.h>
pthread_spinlock_t lock;
int num_jobs;
// called by main
int numJobs()
{
pthread_spin_lock(&lock);
int res = num_jobs;
pthread_spin_unlock(&lock);
return res;
}
// called by thread_func
void decNumJobs()
{
pthread_spin_lock(&lock);
--num_jobs;
pthread_spin_unlock(&lock);
}
int main()
{
pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE);
// the other stuff as before
pthread_spin_destroy(&lock);
return 0;
}
Another alternative would be with pthread_cond_wait and pthread_cond_signal (mainly to avoid the sleep in the while loop, continue after receiving the signal and not based on a fixed amount of time).
e.g.
#include <pthread.h>
int num_jobs;
pthread_cond_t cond;
pthread_mutex_t lock;
void decNumJobs()
{
pthread_mutex_lock(&lock);
if (--num_jobs == 0)
pthread_cond_signal(&cond);
pthread_mutex_unlock(&lock);
}
void* thread_func(void*)
{
//the work to do in the thread function
//before exit decrease counter
decNumJobs();
pthread_exit(0);
}
int main()
{
num_jobs = THREADS;
pthread_cond_init(&cond, NULL);
pthread_mutex_init(&lock, NULL);
pthread_mutex_lock(&lock);
create_threads(THREADS);
pthread_cond_wait(&cond, &lock);
pthread_mutex_unlock(&lock);
join_threads();
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&lock);
return 0;
}
Note: For the sake of simplicity, there is no error checking nor handling. Reading the documentation of the pthread_* functions (return values, interrupted wait, etc) is strongly advised.
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;
}
I have different metatables in my project. But if I create a value A and assign the metatable "X" and creates a second value B and attach metatable "Y", A gets the Y metatable, too! Here is a simplified C function for demonstration:
#include <errno.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
// shoudl create t["obj"] = <userdata> with metatable = "Obj" but gets "OtherType"
int create_table_with_object(lua_State *L)
{
lua_newtable(L);
lua_pushlightuserdata(L, (void*)0x1234);
luaL_setmetatable(L, "Obj"); // This Type is already registered with lua_newmetatable()
lua_setfield(L, -2, "obj");
luaL_newmetatable(L, "OtherType");
lua_pushinteger(L, 70);
lua_setfield(L, -2, "ICameFromOtherType");
lua_pop(L, 1); // just a dummy table
// If we create another userdata object, the first one
// gets the same type as this one!
// Obj -> changes to "OtherType"
// ### CRITICAL SECTION STRT ###
lua_pushlightuserdata(L, (void*)0x5555);
luaL_setmetatable(L, "OtherType");
lua_setglobal(L, "JustADummyObj"); // this removes the value from the stack!
// ### CRITICAL SECTION END ###
return 1;
}
int main(void)
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaL_loadfile(L, "bug.lua");
lua_pushcfunction(L, create_table_with_object);
lua_setglobal(L, "create_table_with_object");
luaL_newmetatable(L, "Obj");
lua_pop(L, 1);
int error;
if(error = lua_pcall(L, 0, 0, 0))
{
fprintf(stderr, "Fatal error: \n");
fprintf(stderr, "%s\n", lua_tostring(L, -1));
return 1;
}
lua_close(L);
return 0;
}
Lua Code:
local a = create_table_with_object()
print(getmetatable(a.obj).__name)
The output is "OtherType" but it should be "Obj". It seems that the second call to lua_setmetatable() overrides the table from other values?!
Ok, solved!
lightuserdata in Lua shares one metatable (instead of one metatable per value). So changing the table for a lightuserdata value changes all other lightuserdata values!
I would like to wrap the C timer (not alarm) and use it within lua, in a way that I
could specify a callback function to be triggered after one second have passed.
In order to use multiple timer, a timer ID and callback will be stored to a table,
but a Segmentation fault occured when 'lua_rawset' was called, so I use stack_dump
check the lua stack, a nil was returned by 'lua_rawget' on line 66(lr_register_timer,
marked by FIXME), what is wrong here? Sorry, my English is poor.
Cheers.
lua code:
local lt = luatimer
lt.register_timer(1, function(id)
io.stdout:write("id" .. id .. "timeout\n");
end)
C code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include "timer.h"
static lua_State *L;
static void stack_dump(lua_State *L, FILE *fp)
{
int i;
int top = lua_gettop(L);
for (i = 1; i <= top; i++) {
int t = lua_type(L, i);
switch (t) {
case LUA_TSTRING: fprintf(fp, "'%s'", lua_tostring(L, i)); break;
case LUA_TBOOLEAN: fprintf(fp, lua_toboolean(L, i) ? "true" : "false"); break;
case LUA_TNUMBER: fprintf(fp, "%g", lua_tonumber(L, i)); break;
default: fprintf(fp, "%s", lua_typename(L, t)); break;
}
fprintf(fp, " ");
}
fprintf(fp, "\n");
}
struct timer_env {
int id;
struct event *ev;
};
static void callback_timer_wrap(int id, void *arg)
{
struct timer_env *env = arg;
/* get timer id table */
lua_pushlightuserdata(L, &L);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_pushinteger(L, env->id);
lua_gettable(L, -2);
/* call lua handler with one result */
lua_pushinteger(L, env->id);
if (lua_pcall(L, 1, 1, 0) == 0) {
lua_pop(L, 1); /* pop result */
}
}
/* id, callback */
static int lr_register_timer(lua_State *L)
{
struct timer_env *env;
int id;
int err;
id = (int)luaL_checkinteger(L, 1);
if (!lua_isfunction(L, 2) || lua_iscfunction(L, 2))
return 0;
/* set lua handler */
lua_pushlightuserdata(L, &L);
lua_rawget(L, LUA_REGISTRYINDEX); /* FIXME */
lua_pushvalue(L, 1); /* key: id */
lua_pushvalue(L, 2); /* value: callback */
stack_dump(L, stderr);
/* FIXME crashed */
lua_rawset(L, -3);
lua_pop(L, 1);
env = malloc(sizeof(*env));
memset(env, 0, sizeof(*env));
env->id = id;
if ((err = register_timer(id, callback_timer_wrap, env)) < 0)
free(env);
lua_pushinteger(L, err);
return 1;
}
static const luaL_Reg luatimer_lib[] = {
{ "register_timer", lr_register_timer },
{ NULL, NULL }
};
static int luaopen_luatimer(lua_State *L)
{
luaL_register(L, "luatimer", luatimer_lib);
/* timer id table */
lua_pushlightuserdata(L, &L); /* key */
lua_newtable(L); /* value */
lua_rawset(L, LUA_REGISTRYINDEX);
return 1;
}
int luaenv_init(void)
{
L = luaL_newstate();
luaL_openlibs(L);
lua_pushcfunction(L, luaopen_luatimer);
lua_pushstring(L, "luatimer");
lua_call(L, 1, 0);
return 0;
}
void luaenv_exit(void)
{
if (L)
lua_close(L);
}
Thank you very much, I make a stupid mistake that I use the same name L in local vars and global vars. I'm Sorry
Thanks greatwold and immibis
below is the lua table i need to read from C:
listen = {
{ port = 1234, address = "192.168.1.1", userdata = "liunx" },
{ port = 1235, address = "192.168.1.2", userdata = "liunx1" },
{ port = 1236, address = "192.168.1.3", userdata = "liunx2" }
}
below is the c code:
#include <lua.h> /* Always include this when calling Lua */
#include <lauxlib.h> /* Always include this when calling Lua */
#include <lualib.h> /* Prototype for luaL_openlibs(), */
/* always include this when calling Lua */
#include <stdlib.h> /* For function exit() */
#include <stdio.h> /* For input/output */
void bail(lua_State *L, char *msg){
fprintf(stderr, "\nFATAL ERROR:\n %s: %s\n\n",
msg, lua_tostring(L, -1));
exit(1);
}
int main(void)
{
lua_State *L;
L = luaL_newstate(); /* Create Lua state variable */
luaL_openlibs(L); /* Load Lua libraries */
if (luaL_loadfile(L, "cfg.lua"))
bail(L, "luaL_loadfile() failed");
if (lua_pcall(L, 0, 0, 0))
bail(L, "lua_pcall() failed");
// how to read???
lua_getglobal(L, "listen");
lua_close(L);
return 0;
}
I want to travel this table which may contain a few number of data in while loop, but really do not know how to do it, so any tips?
Thanks very much for your tips!Below are the worked code:
#include <lua.h> /* Always include this when calling Lua */
#include <lauxlib.h> /* Always include this when calling Lua */
#include <lualib.h> /* Prototype for luaL_openlibs(), */
/* always include this when calling Lua */
#include <stdlib.h> /* For function exit() */
#include <stdio.h> /* For input/output */
void bail(lua_State *L, char *msg)
{
fprintf(stderr, "\nFATAL ERROR:\n %s: %s\n\n",
msg, lua_tostring(L, -1));
exit(1);
}
int main(void)
{
lua_State *L;
static struct {
const char * name;
int type;
} fields[] = {
{"port", LUA_TNUMBER},
{"address", LUA_TSTRING},
{"userdata", LUA_TSTRING},
{NULL, 0}
};
L = luaL_newstate(); /* Create Lua state variable */
luaL_openlibs(L); /* Load Lua libraries */
if (luaL_loadfile(L, "cfg.lua"))
bail(L, "luaL_loadfile() failed");
if (lua_pcall(L, 0, 0, 0))
bail(L, "lua_pcall() failed");
lua_getglobal(L, "listen");
luaL_checktype(L, -1, LUA_TTABLE);
int i;
for (i = 1; ; i++) {
lua_rawgeti(L, -1, i);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
break;
}
// an element of the 'listen' table should now be at the top of the stack
luaL_checktype(L, -1, LUA_TTABLE);
// read the content of that element
int field_index;
for (field_index = 0; (fields[field_index].name != NULL
&& fields[field_index].name != NULL); field_index++) {
lua_getfield(L, -1, fields[field_index].name);
luaL_checktype(L, -1, fields[field_index].type);
// you should probably use a function pointer in the fields table.
// I am using a simple switch/case here
switch(field_index) {
case 0:
printf("port: %d\n", (int)lua_tonumber(L, -1));
// do what you want with port
break;
case 1:
printf("address: %s\n", lua_tostring(L, -1));
break;
case 2:
// handle userdata
printf("userdata: %s\n", lua_tostring(L, -1));
break;
}
// remove the field value from the top of the stack
lua_pop(L, 1);
}
// remove the element of the 'listen' table from the top of the stack.
lua_pop(L, 1);
}
lua_close(L);
return 0;
}
You are not too far. The key here is to understand how the Lua API use the stack for everything.
Here is an untested code sample which should get you going:
// this will be used to validate our table
static struct {
const char * name;
int type;
} fields[] = {
{"port", LUA_TNUMBER},
{"address", LUA_TSTRING},
{"userdata", LUA_TSTRING},
NULL
};
lua_getglobal(L, "listen");
// the 'listen' table should be at the top of the stack
luaL_checktype(L, -1, LUA_TTABLE);
// iterate the listen table
int i;
for (i = 1; ; i++) {
lua_rawgeti(L, -1, i);
// when you get nil, you're done
if (lua_isnil(L, -1)) {
lua_pop(L,1);
break;
}
// an element of the 'listen' table should now be at the top of the stack
luaL_checktype(L, -1, LUA_TTABLE);
// read the content of that element
int field_index;
for (field_index = 0; fields[field_index] != NULL; field_index++) {
lua_getfield(L, -1, fields[field_index].name);
luaL_checktype(L, -1, fields[field_index].type);
// you should probably use a function pointer in the fields table.
// I am using a simple switch/case here
switch(field_index) {
case 0:
port = lua_tonumber(L, -1);
// do what you want with port
break;
case 1:
address = lua_tostring(L, -1);
break;
case 2:
// handle userdata
break;
}
// remove the field value from the top of the stack
lua_pop(L, 1);
}
// remove the element of the 'listen' table from the top of the stack.
lua_pop(L, 1);
}
I suggest you use those documentations: Lua API table Lua API ref