Pthread Readers and Writers
#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);
}