Rendezvous: Difference between revisions
Content added Content deleted
Thundergnat (talk | contribs) m (→{{header|Raku}}: fix up Perl 6 -> Raku in comments, minor update) |
(→{{header|C}}: Add C implementation using pthreads) |
||
Line 224: | Line 224: | ||
=={{header|C}}== |
=={{header|C}}== |
||
== Pthreads implementation == |
|||
{{works with|gcc|10.1.0}} |
|||
{{libheader|pthread}} |
|||
This uses POSIX threads to implement a subset of the Ada functionality and primarily focuses on the synchronization aspect. C does not have exceptions, so return values are used to signal errors. Multiple threads can enter a rendezvous at once, and a single thread can accept them. No attempt is made to implement selective accept statements or timeouts (though pthreads does have ''pthread_cond_timedwait()''). |
|||
<lang C> |
|||
#include <stdlib.h> |
|||
#include <stdio.h> |
|||
#include <pthread.h> |
|||
/* 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; |
|||
} |
|||
</lang> |
|||
== OpenMP implementation == |
|||
Basically just synched threads doing printing: since task didn't ask for service type or resource enumeration, and "message passing is stupid" (c.f. talk), the guarding thread is no more than a glorified mutex, hence completely cut out, leaving the threads directly check ink and do print. |
Basically just synched threads doing printing: since task didn't ask for service type or resource enumeration, and "message passing is stupid" (c.f. talk), the guarding thread is no more than a glorified mutex, hence completely cut out, leaving the threads directly check ink and do print. |
||
<lang C>#include <stdio.h> |
<lang C>#include <stdio.h> |