Saturday, November 05, 2011

Playing with Pthread

In my scientific work, I mostly deal with embarrassingly parallel problems like parameters sweep which consists of many independent problems that doesn't have to communicate with each other. MATLAB's parfor is an easy way to handle these. But this got me interested in learning about different way to do parallel computing. Having a new quad-core laptop also helps (Thinkpad x201-tablet). Since I am also brushing up on the subject stochastic process, so I decide to play with some simple code for something akin to throwing a dice many times . There are many way to do parallel computing. I start with POSIX Thread since it seems ubiquitous to all platform and compilers.

So first, write a function to be ran concurrently:

/* read from file f, read until either N maches of x or EOF is reached
 * return number of chars read
 */
int countchar(FILE * f, char x, int N);

To create a thread, one uses the function pthread_create(). It has the following signature:

/* thread - returns the thread id. (unsigned long int defined in bits/pthreadtypes.h)
 * attr - Set to NULL if default thread attributes are used.
 * void * (*start_routine) - pointer to the function to be threaded. Function has a single argument: pointer to void.
 * arg - pointer to argument of function. To pass multiple arguments, send a pointer to a structure.
 */
int pthread_create(pthread_t * thread, 
                   const pthread_attr_t * attr,
                   void * (*start_routine)(void *), 
                   void *arg);
The comment is pretty self-explanatory. The thing to watch out for is that the third argument expects pointer to function with a particular signature, but the routine I want to run has a different one. So write the following code to wrap the countchar function:
/* a struct to wrap around arguments of countchar function */
struct cc_thread_ {
    FILE * f;
    char x;
    int N;
    int m;
    int thread_num;
};
typedef struct cc_thread_ cc_thread;

void * countchar_thread(void * cc_t) {
    cc_thread * my_cc = (cc_thread *) cc_t;

    printf("Thread %d to read char %c from %p up to %d times.\n",
           my_cc->thread_num, my_cc->x, my_cc->f, my_cc->N);
    my_cc->m = countchar(my_cc->f, my_cc->x, my_cc->N);
    printf("Got %d '%c's after %d chars\n", my_cc->N, my_cc->x, my_cc->m);

    pthread_exit(NULL);
}
The last line of course tells the parent thread that the current thread is done. Now all we have to do is write a main():
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define NUM_THREADS 4

...

int main(void) {
    int j;
    int rc; // for return code of pthread_create
    pthread_t threads[NUM_THREADS];
    cc_thread cc_t[NUM_THREADS];

    for (j=0; j < NUM_THREADS; j++) {
        cc_t[j].f = fopen("/dev/urandom","r");
        cc_t[j].x = 'a' + j;
        cc_t[j].N = 50000;
        cc_t[j].thread_num = j;

        // create the j-th thread
        rc = pthread_create(&threads[j], NULL, countchar_thread, &(cc_t[j]));
        if (rc) {
            printf("ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
    }

    pthread_exit(NULL); // w/o this, main rush to finish.

    for (j=0; j < NUM_THREADS; j++) {
        fclose(cc_t[j].f);
    }

    return 0;
}

Compiling and Running:

$ make
gcc -c -o cc_pthread.o -Wall -c cc_pthread.c
gcc -c -o charcount.o -Wall -c charcount.c
gcc    -o cc_thread -pthread  cc_pthread.o charcount.o
$ ./cc_thread 
Thread 0 to read char a from 0x1209010 up to 50000 times.
Thread 2 to read char c from 0x12096d0 up to 50000 times.
Thread 3 to read char d from 0x1209a30 up to 50000 times.
Thread 1 to read char b from 0x1209370 up to 50000 times.
Got 50000 'b's after 12751512 chars
Got 50000 'd's after 12685986 chars
Got 50000 'a's after 12765702 chars
Got 50000 'c's after 12816189 chars
Notice that both printf that sandwiches the countchar do no necessarily print in order.

Up to this point, the threads of this program still don't talk to each other. I will do some simple message passing next time

No comments: