0

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);
}
`
Pablots99
  • 5
  • 1

2 Answers2

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`
   */
   ....
}
HAL9000
  • 2,138
  • 1
  • 9
  • 20
0

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.

Erdal Küçük
  • 4,810
  • 1
  • 6
  • 11