Home > OS >  Printing in order with c threads
Printing in order with c threads

Time:06-18

Given this code:

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

void *findPrimes(void *arg)
{
    int val = *(int *)arg;
    for (int i = val * 1000; i < val * 1000   1000; i  )
    {
        int isPrime = 1;
        for (int j = 2; j < i; j  )
        {
            if (i % j == 0)
            {
                isPrime = 0;
                break;
            }
        }
        if (isPrime)
        {
            printf("%d\n", i);
        }
    }
    pthread_exit(NULL);
}
int main()
{
    pthread_t p[3];

    int val[3] = {0, 1, 2};
    for (int i = 0; i < 3; i  )
    {
        pthread_create(&p[i], NULL, findPrimes, &val[i]);
    }
    for (int i = 0; i < 3; i  )
    {
        pthread_join(p[i], NULL);
    }

    return 0;
}

Who prints in 3 threads all the prime number between 0 and 3000.

I want to print them in order, how can i do it? My professor suggest to use an array of semaphore.

CodePudding user response:

In order to synchronize the actions of all the threads I suggest using a pthread_mutex_t and a pthread_cond_t (a condition variable). You also need a way to share data between threads, so I'd create a struct for that:

#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>

typedef struct {
    unsigned whos_turn;
    pthread_mutex_t mtx;
    pthread_cond_t cv;
} shared_data;

whos_turn will here be used to tell the threads whos turn it is to print the primes found.

Each thread also needs some thread-unique information. You called it val so I'll call it val here too. We can compare val with whos_turn to decide which thread it is that should print its result. In order to pass both the shared data and val to a thread, you can package that in a struct too:

typedef struct {
    unsigned val;
    shared_data *sd; // will point to the one and only instance of `shared_data`
} work_order;

Now, findPrimes need somewhere to store the primes it calculates before it's time to print them. Since the range to search is hardcoded, I'd just add an array for that:

#define SEARCH_RANGE (1000ULL)

void *findPrimes(void *arg) {
    work_order *wo = arg;
    
    uintmax_t primes[SEARCH_RANGE]; // to store the found primes
    int found_count = 0;
    
    for (uintmax_t i = wo->val*SEARCH_RANGE 1; i <= (wo->val 1)*SEARCH_RANGE; i  = 2) {
        bool isPrime = true;
        for (uintmax_t j = 3; j < i; j  = 2) {
            if (i % j == 0) {    // note: both i and j are odd
                isPrime = false;
                break;
            }
        }
        if (isPrime) {
            primes[found_count  ] = i;
        }
    }
    if(wo->val == 0) {  // special case for the first range
        primes[0] = 2;  // 1 is not a prime, but 2 is.
    }
    // ... to be continued below ...

So far, nothing spectacular. The thread has now found all primes in its range and has come to the synchronizing part. The thread must

  • lock the mutex
  • wait for its turn (called "the predicate")
  • let other threads do the same

Here's one common pattern:

    // ... continued from above ...

    // synchronize
    pthread_mutex_lock(&wo->sd->mtx);      // lock the mutex
    // only 1 thread at a time reaches here
    // check the predicate: That is's this thread's turn to print
    while(wo->val != wo->sd->whos_turn) {  // <- the predicate
        // if control enters here, it was not this thread's turn

        // cond_wait internally "unlocks" the mutex to let other threads
        // reach here and wait for the condition variable to get signalled
        pthread_cond_wait(&wo->sd->cv, &wo->sd->mtx);

        // and here the lock is only held by one thread at a time again
    }
    // only the thread whos turn it is reaches here

Now, the thread has reached the point where it is its time to print. It has the mutex lock so no other threads can reach this point at the same time.

    // print the collected primes
    for(int i = 0; i < found_count;   i)
        printf("%ju\n", primes[i]);

And hand over to the next thread in line to print the primes it has found:

    // step the "whos_turn" indicator
    wo->sd->whos_turn  ;

    pthread_mutex_unlock(&wo->sd->mtx);  // release the mutex
    pthread_cond_broadcast(&wo->sd->cv); // signal all threads to check the predicate
    pthread_exit(NULL);
}

And it can be tied together quite neatly in main:

#define Size(x) (sizeof (x) / sizeof *(x))

int main() {
    shared_data sd = {.whos_turn = 0,
                      .mtx = PTHREAD_MUTEX_INITIALIZER,
                      .cv = PTHREAD_COND_INITIALIZER};
    pthread_t p[3];
    
    work_order wos[Size(p)];

    for (unsigned i = 0; i < Size(p); i  ) {
        wos[i].val = i;  // the thread-unique information
        wos[i].sd = &sd; // all threads will point at the same `shared_data`
        pthread_create(&p[i], NULL, findPrimes, &wos[i]);
    }
    for (unsigned i = 0; i < Size(p); i  ) {
        pthread_join(p[i], NULL);
    }
}

Demo

  • Related