#include #include #include /* The language task, implemented with pthreads for POSIX systems. */ /* Each rendezvous_t will be accepted by a single thread, and entered * by one or more threads. accept_func() only returns an integer and * is always run within the entering thread's context to simplify * handling the arguments and return value. This somewhat unlike an * Ada rendezvous and is a subset of the Ada rendezvous functionality. * Ada's in and out parameters can be simulated via the void pointer * passed to accept_func() to update variables owned by both the * entering and accepting threads, if a suitable struct with pointers * to those variables is used. */ typedef struct rendezvous { pthread_mutex_t lock; /* A mutex/lock to use with the CVs. */ pthread_cond_t cv_entering; /* Signaled when a thread enters. */ pthread_cond_t cv_accepting; /* Signaled when accepting thread is ready. */ pthread_cond_t cv_done; /* Signaled when accept_func() finishes. */ int (*accept_func)(void*); /* The function to run when accepted. */ int entering; /* Number of threads trying to enter. */ int accepting; /* True if the accepting thread is ready. */ int done; /* True if accept_func() is done. */ } rendezvous_t; /* Static initialization for rendezvous_t. */ #define RENDEZVOUS_INITILIZER(accept_function) { \ .lock = PTHREAD_MUTEX_INITIALIZER, \ .cv_entering = PTHREAD_COND_INITIALIZER, \ .cv_accepting = PTHREAD_COND_INITIALIZER, \ .cv_done = PTHREAD_COND_INITIALIZER, \ .accept_func = accept_function, \ .entering = 0, \ .accepting = 0, \ .done = 0, \ } int enter_rendezvous(rendezvous_t *rv, void* data) { /* Arguments are passed in and out of the rendezvous via * (void*)data, and the accept_func() return value is copied and * returned to the caller (entering thread). A data struct with * pointers to variables in both the entering and accepting * threads can be used to simulate Ada's in and out parameters, if * needed. */ pthread_mutex_lock(&rv->lock); rv->entering++; pthread_cond_signal(&rv->cv_entering); while (!rv->accepting) { /* Nothing is accepting yet, keep waiting. pthreads will * queue all waiting entries. The loop is needed to handle * both race conditions and spurious wakeups. */ pthread_cond_wait(&rv->cv_accepting, &rv->lock); } /* Call accept_func() and copy the return value before leaving * the mutex. */ int ret = rv->accept_func(data); /* This signal is needed so that the accepting thread will wait * for the rendezvous to finish before trying to accept again. */ rv->done = 1; pthread_cond_signal(&rv->cv_done); rv->entering--; rv->accepting = 0; pthread_mutex_unlock(&rv->lock); return ret; } void accept_rendezvous(rendezvous_t *rv) { /* This accept function does not take in or return parameters. * That is handled on the entry side. This is only for * synchronization. */ pthread_mutex_lock(&rv->lock); rv->accepting = 1; while (!rv->entering) { /* Nothing to accept yet, keep waiting. */ pthread_cond_wait(&rv->cv_entering, &rv->lock); } pthread_cond_signal(&rv->cv_accepting); while (!rv->done) { /* Wait for accept_func() to finish. */ pthread_cond_wait(&rv->cv_done, &rv->lock); } rv->done = 0; rv->accepting = 0; pthread_mutex_unlock(&rv->lock); } /* The printer use case task implemented using the above rendezvous * implementation. Since C doesn't have exceptions, return values are * used to signal out of ink errors. */ typedef struct printer { rendezvous_t rv; struct printer *backup; int id; int remaining_lines; } printer_t; typedef struct print_args { struct printer *printer; const char* line; } print_args_t; int print_line(printer_t *printer, const char* line) { print_args_t args; args.printer = printer; args.line = line; return enter_rendezvous(&printer->rv, &args); } int accept_print(void* data) { /* This is called within the rendezvous, so everything is locked * and okay to modify. */ print_args_t *args = (print_args_t*)data; printer_t *printer = args->printer; const char* line = args->line; if (printer->remaining_lines) { /* Print the line, character by character. */ printf("%d: ", printer->id); while (*line != '\0') { putchar(*line++); } putchar('\n'); printer->remaining_lines--; return 1; } else if (printer->backup) { /* "Requeue" this rendezvous with the backup printer. */ return print_line(printer->backup, line); } else { /* Out of ink, and no backup available. */ return -1; } } printer_t backup_printer = { .rv = RENDEZVOUS_INITILIZER(accept_print), .backup = NULL, .id = 2, .remaining_lines = 5, }; printer_t main_printer = { .rv = RENDEZVOUS_INITILIZER(accept_print), .backup = &backup_printer, .id = 1, .remaining_lines = 5, }; void* printer_thread(void* thread_data) { printer_t *printer = (printer_t*) thread_data; while (1) { accept_rendezvous(&printer->rv); } } typedef struct poem { char* name; char* lines[]; } poem_t; poem_t humpty_dumpty = { .name = "Humpty Dumpty", .lines = { "Humpty Dumpty sat on a wall.", "Humpty Dumpty had a great fall.", "All the king's horses and all the king's men", "Couldn't put Humpty together again.", "" }, }; poem_t mother_goose = { .name = "Mother Goose", .lines = { "Old Mother Goose", "When she wanted to wander,", "Would ride through the air", "On a very fine gander.", "Jack's mother came in,", "And caught the goose soon,", "And mounting its back,", "Flew up to the moon.", "" }, }; void* poem_thread(void* thread_data) { poem_t *poem = (poem_t*)thread_data; for (unsigned i = 0; poem->lines[i] != ""; i++) { int ret = print_line(&main_printer, poem->lines[i]); if (ret < 0) { printf(" %s out of ink!\n", poem->name); exit(1); } } return NULL; } int main(void) { pthread_t threads[4]; pthread_create(&threads[0], NULL, poem_thread, &humpty_dumpty); pthread_create(&threads[1], NULL, poem_thread, &mother_goose); pthread_create(&threads[2], NULL, printer_thread, &main_printer); pthread_create(&threads[3], NULL, printer_thread, &backup_printer); pthread_join(threads[0], NULL); pthread_join(threads[1], NULL); pthread_cancel(threads[2]); pthread_cancel(threads[3]); return 0; }