#include #include #include #include #include /* * 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); }