------------------------------------------------------------------------------
MC logo
Pthread Readers and Writers
[^] Process and Thread Synchronization
------------------------------------------------------------------------------
rw.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pthread.h>

/*
 * Pthreads doesn't actually have semaphores.  It has mutexes and condition variables,
 * which we can use to make a semaphore.
 */
struct semaphore {
        int value;
        pthread_mutex_t mux;            // Controls access.
        pthread_cond_t waitcond;        // Controls waiting and restart
};
#define SEMAPHORE_INITIALIZER(x) { x, PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER }

/* Semaphore down operation. */
void down(struct semaphore *s)
{
        pthread_mutex_lock(&s->mux);
        if(--s->value < 0) pthread_cond_wait(&s->waitcond, &s->mux);
        pthread_mutex_unlock(&s->mux);
}
// Note: the mutex is released during the cond_wait, and reaquired when the wait ends.

/* Semaphore up operation. */
void up(struct semaphore *s)
{
        pthread_mutex_lock(&s->mux);
        if(++s->value <= 0) pthread_cond_signal(&s->waitcond);
        pthread_mutex_unlock(&s->mux);
}

/*
 * This is the shared data structure, a un-ordered linked list of names and values.
 */
struct data_node {
        struct data_node *next;
        int value;
        char name[1];
} *data_list = NULL;

/*
 * Readers and writers control.  The basic mutex will do for the mutex semaphore, and
 * the write semphore is one created above.
 */
pthread_mutex_t readcount_mutex = PTHREAD_MUTEX_INITIALIZER;
struct semaphore writesem = SEMAPHORE_INITIALIZER(1);
int readcount = 0;

/*
 * Operations to synchronize readers and writers.
 */
void enter_read()
{
        pthread_mutex_lock(&readcount_mutex);
        readcount++;
        if(readcount == 1)
                down(&writesem);
        pthread_mutex_unlock(&readcount_mutex);
}

void leave_read()
{
        pthread_mutex_lock(&readcount_mutex);
        readcount--;
        if(readcount == 0)
                up(&writesem);
        pthread_mutex_unlock(&readcount_mutex);
}

void enter_write()
{
        down(&writesem);
}

void leave_write()
{
        up(&writesem);
}

/*
 * Call rand() under a mutex lock.  Not safe to call from threads unguarded.
 */
int locked_rand()
{
        static pthread_mutex_t mux = PTHREAD_MUTEX_INITIALIZER;
        pthread_mutex_lock(&mux);
        int ret = rand();
        pthread_mutex_unlock(&mux);
        return ret;
}

/*
 * Choose one of the strings from a list at random.
 */
const char *rand_name()
{
        static const char *names[] = {
                "international", "consolidated", "confederated", "amalgomated",
                "fubar", "reinitialized", "discombobulated", "missing", 
                "fiduciary", "exasperated", "laminated", "embalmed" };
        int which = locked_rand() % (sizeof names / sizeof names[0]);
        return names[which];
}

/*
 * Some readers.
 */

/*
 * Find a random name.
 */
void *finder(void *p)
{
        const char *tofind = rand_name();

        enter_read();
        struct data_node *scan = data_list;
        while(scan != NULL) {
                if(strcmp(tofind, scan->name) == 0) break;
                scan = scan->next;
        }
        if(scan)
                printf("%s is valued at %d\n", scan->name, scan->value);
        else
                printf("%s is not listed.\n", tofind);
        leave_read();
}

/*
 * Find the average value.
 */
void *av(void *p)
{
        int ct = 0;
        int sum = 0;
        enter_read();
        struct data_node *scan;
        for(scan = data_list; scan; scan = scan->next)
        {
                sum += scan->value;
                ++ct;
        }
        leave_read();

        if(ct > 0)
                printf("Average is %g\n", (double)sum / (double)ct);
        else
                printf("No items to average\n");
}

/*
 * Find the range.
 */
void *range(void *p)
{
        enter_read();
        struct data_node *scan = data_list;
        if(scan == NULL) {
                printf("No items for range computation.\n");
                leave_read();
        } else {
                int min = scan->value;
                int max = scan->value;
                for(scan = scan->next; scan; scan = scan->next)
                {
                        if(min > scan->value) min = scan->value;
                        if(max < scan->value) max = scan->value;
                }
                leave_read();

                printf("Range is %d to %d\n", min, max);
        }
}

/*
 * Update a random item.  The thread starting call passes only one parameter, so
 * it is common to use a struct (in plain C) or an object (in C++).  This one is
 * allocated dynamically, though the need for that varies.
 */
struct update_record {
        int increment;
        const char *which;
};
void *update(void *ptr)
{
        struct update_record *uprec = (struct update_record *)ptr;

        int created = 0;

        enter_write();

        /* Find in the list. */
        struct data_node *scan;
        for(scan = data_list; scan != NULL; scan = scan->next) {
                if(strcmp(uprec->which, scan->name) == 0) {
                        scan->value += uprec->increment;
                        break;
                }
        }
        if(!scan)
        {
                // Add to the front of the list.  This creates a new node using a
                // a dirty C trick to accomodate nodes with variable-length plain 
                // C strings.  The struct is defined with a one-character string,
                // and we allocate extra space to hold the whole string.
                struct data_node *newnode = 
                        malloc(sizeof (struct data_node) + strlen(uprec->which));
                strcpy(newnode->name, uprec->which);
                newnode->value = uprec->increment;
                newnode->next = data_list;
                data_list = newnode;

                created = 1;
        }

        leave_write();

        printf("%s %s %d\n", uprec->which, created ? "created with" : "given", 
               uprec->increment);

        free(ptr);
}

/*
 * Some writers. 
 */

/*
 * Delete a random item, if present.  If it picks a name which is not in the list,
 * does nothing.  Form of the scan uses some dirty C pointer tricks to avoid having
 * to special case deleting the first item.
 */
void *remove_node(void *p)
{
        const char *which = rand_name();

        enter_write();

        /* Scan the list to look for the randomly-chosen name.  Keeps track of a
           pointer to the link, not the link itself.  Makes updates easier.  Can
           modify the head pointer to delete the first node without needing a special
           case. */
        struct data_node **scan = &data_list;
        while(*scan != NULL && strcmp((*scan)->name, which) != 0)
                scan = &(*scan)->next;

        /* If something was found, remove it. */
        int was_deleted = 0;
        if(*scan != NULL) {
                struct data_node *zombie = *scan;
                *scan = (*scan)->next;
                free(zombie);

                was_deleted = 1;
        }

        leave_write();

        /* Let us know. */
        if(was_deleted)
                printf("%s deleted\n", which);
        else
                printf("%s could not be found\n", which);
}

/* Start one of the thread functions at random. */
void start_thread(pthread_t *id)
{
        // Create a thread for one of the actors, at random, with different
        // probabilities.
        switch(locked_rand() % 11)
        {
        case 0:
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
                pthread_create(id, NULL, finder, NULL);
                break;
        case 6:
                pthread_create(id, NULL, av, NULL);
                break;
        case 7:
                pthread_create(id, NULL, range, NULL);
                break;
        case 8:
        case 9: {
                        struct update_record *rec = 
                                malloc(sizeof (struct update_record));
                        rec->increment = locked_rand() % 100 - 10;
                        rec->which = rand_name();
                        pthread_create(id, NULL, update, rec);
                        break;
                }
        case 10:
                pthread_create(id, NULL, remove_node, NULL);
                break;
        }
}

int main(int argc, char **argv)
{
        int nthread = 10;
        int niter = 10;

        if(argc > 1) nthread = atoi(argv[1]);
        if(argc > 2) niter = atoi(argv[2]);

        srand(time(NULL));

        /* Create nthread threads, replacing so that niter are created for 
           each slot. */
        pthread_t *thread_list = malloc(nthread *sizeof(pthread_t));

        /* Create the first bank. */
        int i;
        for(i = 0; i < nthread; ++i)
                start_thread(&thread_list[i]);

        /* Do the medial ones */
        for(i = 0; i < niter - 2; ++i)
        {
                int j;
                for(j = 0; j < nthread; ++j) {
                        pthread_join(thread_list[j], NULL);
                        start_thread(&thread_list[j]);
                }
        }

        /* Final cleanup. */
        for(i = 0; i < nthread; ++i)
                pthread_join(thread_list[i], NULL);
}