commit a5ec64177dbb020c327a7f720508c5b732b70e4f Author: Hailin Date: Thu Mar 28 10:34:22 2013 +0800 initial import from http://www.informit.com/store/programming-with-posix-threads-9780201633924 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..485587b --- /dev/null +++ b/Makefile @@ -0,0 +1,50 @@ +# Digital UNIX 4.0 compilation flags: +CFLAGS=-std1 -pthread -g -w1 $(DEBUGFLAGS) +RTFLAGS=-lrt + +# Solaris 2.5 compilation flags: +#CFLAGS=-D_POSIX_C_SOURCE=199506 -D_REENTRANT -Xa -lpthread -g $(DEBUGFLAGS) +#RTFLAGS=-lposix4 + +SOURCES=alarm.c alarm_cond.c alarm_fork.c alarm_mutex.c \ + alarm_thread.c atfork.c backoff.c \ + barrier_main.c cancel.c cancel_async.c cancel_cleanup\ + cancel_disable.c cancel_subcontract.c cond.c cond_attr.c \ + crew.c cond_dynamic.c cond_static.c flock.c getlogin.c hello.c \ + inertia.c lifecycle.c mutex_attr.c \ + mutex_dynamic.c mutex_static.c once.c pipe.c putchar.c \ + rwlock_main.c rwlock_try_main.c \ + sched_attr.c sched_thread.c semaphore_signal.c \ + semaphore_wait.c server.c sigev_thread.c \ + sigwait.c susp.c thread.c \ + thread_attr.c thread_error.c trylock.c tsd_destructor.c \ + tsd_once.c workq_main.c +PROGRAMS=$(SOURCES:.c=) +all: ${PROGRAMS} +alarm_mutex: + ${CC} ${CFLAGS} ${RTFLAGS} ${LDFLAGS} -o $@ alarm_mutex.c +backoff: + ${CC} ${CFLAGS} ${RTFLAGS} ${LDFLAGS} -o $@ backoff.c +sched_attr: + ${CC} ${CFLAGS} ${RTFLAGS} ${LDFLAGS} -o $@ sched_attr.c +sched_thread: + ${CC} ${CFLAGS} ${RTFLAGS} ${LDFLAGS} -o $@ sched_thread.c +semaphore_signal: + ${CC} ${CFLAGS} ${RTFLAGS} ${LDFLAGS} -o $@ semaphore_signal.c +semaphore_wait: + ${CC} ${CFLAGS} ${RTFLAGS} ${LDFLAGS} -o $@ semaphore_wait.c +sigev_thread: + ${CC} ${CFLAGS} ${RTFLAGS} ${LDFLAGS} -o $@ sigev_thread.c +susp: + ${CC} ${CFLAGS} ${RTFLAGS} ${LDFLAGS} -o $@ susp.c +rwlock_main: rwlock.c rwlock.h rwlock_main.c + ${CC} ${CFLAGS} ${LDFLAGS} -o $@ rwlock_main.c rwlock.c +rwlock_try_main: rwlock.h rwlock.c rwlock_try_main.c + ${CC} ${CFLAGS} ${LDFLAGS} -o $@ rwlock_try_main.c rwlock.c +barrier_main: barrier.h barrier.c barrier_main.c + ${CC} ${CFLAGS} ${LDFLAGS} -o $@ barrier_main.c barrier.c +workq_main: workq.h workq.c workq_main.c + ${CC} ${CFLAGS} ${RTFLAGS} ${LDFLAGS} -o $@ workq_main.c workq.c +clean: + @rm -rf $(PROGRAMS) *.o +recompile: clean all diff --git a/README b/README new file mode 100644 index 0000000..0760e53 --- /dev/null +++ b/README @@ -0,0 +1,100 @@ +These are the source files for the programming examples in +"Programming With POSIX(r) Threads". The Makefile is pre-configured +for Digital UNIX, but includes the appropriate definitions to build on +Solaris (uncomment the Solaris lines and comment the Digital UNIX +lines). + +alarm.c Simple synchronous alarm clock +alarm_cond.c Threaded alarm clock using condition variable +alarm_fork.c Alarm clock using fork asychrony +alarm_mutex.c Threaded alarm clock using mutex +alarm_thread.c Alarm clock using thread asynchrony +atfork.c Demonstrate pthread_atfork() +backoff.c Demonstrate mutex hierarchy backoff +barrier.c Implementation of barrier package +barrier_main.c Demonstrate use of barrier package +cancel.c Demonstrate cancellation +cancel_async.c Demonstrate asyncronous cancellation +cancel_cleanup.c Demonstrate cancellation cleanup +cancel_disable.c Demonstrate disabling cancellation +cancel_subcontract.c Demonstrate cancellation of "subcontractors" +cond.c Demonstrate use of condition variables +cond_attr.c Demonstrate condition variable attributes +cond_dynamic.c Demonstrate dynamic init of condition variable +cond_static.c Demonstrate static init of condition variable +crew.c A simple threaded work crew +flock.c Demonstrate use of file locking +getlogin.c Demonstrate reentrant user functions +hello.c Demonstrate thread creation +inertia.c Demonstrate "thread inertia" errors +lifecycle.c Demonstrate "thread lifecycle" +mutex_attr.c Demonstrate mutex attributes +mutex_dynamic.c Demonstrate dynamic initialization of mutex +mutex_static.c Demonstrate static initialization of mutex +once.c Demonstrate use of pthread_once() +pipe.c A simple threaded pipeline +putchar.c Demonstrate thread-safe use of putchar() +rwlock.c Implementation of read/write lock package +rwlock_main.c Demonstrate use of read/write lock package +rwlock_try_main.c Demonstrate use of read/write lock package +sched_attr.c Demonstrate thread scheduling attributes +sched_thread.c Demonstrate use of thread scheduling functions +semaphore_signal.c Demonstrate use of semaphores with signals +semaphore_wait.c Demonstrate use of semaphores +server.c A simple threaded client/server program +sigev_thread.c Demonstrate use of SIGEV_THREAD mechanism +sigwait.c Demonstrate use of sigwait() +susp.c Demonstrate use of pthread_kill() +thread.c Demonstrate simple concurrent I/O +thread_attr.c Demonstrate thread attributes +thread_error.c Demonstrate POSIX thread error mechanism +trylock.c Demonstrate use of pthread_mutex_trylock() +tsd_destructor.c Demonstrate thread-specific data destructors +tsd_once.c Demonstrate thread-specific data key creation +workq.c Implementation of work queue package +workq_main.c Demonstrate use of work queue package + +Header files: + +barrier.h Definitions for barrier package +errors.h General headers and error macros +rwlock.h Definitions for read/write lock package +workq.h Definitions for work queue package + +Programs with arguments or special behavior: + +alarm, alarm_fork, The alarm programs will prompt for +alarm_thread, alarm_mutex, commands until terminated by ^D +alarm_cond (EOF). Commands are " " where + (the remainder of the line) is a + message to print after seconds. +atfork [hang] Run with an argument of 0 to omit + atfork handlers -- program will hang. +backoff [backoff [delay]] Run with first argument of 0, will not + back off on mutex collision, and will + usually hang. Second argument can be + specified greater than 0 to yield + (increasing chances of hang on + uniprocessor), or less than 0 to sleep + for a second. +crew string path First argument is a search string, + second is a file path. +flock Threads will prompt alternately for + input. +pipe Prompts for integers to feed to + pipeline; enter "=" to pop a result. +putchar [unsync] Run with argument of 0 to concurrently + call putchar_unlocked from multiple + threads. +server Threads each prompt for input, and + echo it 3 times -- server prevents + output while waiting for input. +sigwait Waits for 5 SIGINT signals (^C) +thread One thread writes to stdout while + another waits for input from + stdin. (Satisfy the read to exit.) + +/---[ Dave Butenhof ]-----------------------[ butenhof@zko.dec.com ]---\ +| Digital Equipment Corporation 110 Spit Brook Rd ZKO2-3/Q18 | +| 603.881.2218, FAX 603.881.0120 Nashua NH 03062-2698 | +\-----------------[ Better Living Through Concurrency ]----------------/ diff --git a/alarm.c b/alarm.c new file mode 100644 index 0000000..c1a572a --- /dev/null +++ b/alarm.c @@ -0,0 +1,34 @@ +/* + * alarm.c + * + * Simple synchronous alarm program. This is used as a + * reference for progressive examples of asynchronous + * alarm programs. + */ +#include "errors.h" + +int main (int argc, char *argv[]) +{ + int seconds; + char line[128]; + char message[64]; + + while (1) { + printf ("Alarm> "); + if (fgets (line, sizeof (line), stdin) == NULL) exit (0); + if (strlen (line) <= 1) continue; + + /* + * Parse input line into seconds (%d) and a message + * (%64[^\n]), consisting of up to 64 characters + * separated from the seconds by whitespace. + */ + if (sscanf (line, "%d %64[^\n]", + &seconds, message) < 2) { + fprintf (stderr, "Bad command\n"); + } else { + sleep (seconds); + printf ("(%d) %s\n", seconds, message); + } + } +} diff --git a/alarm_cond.c b/alarm_cond.c new file mode 100644 index 0000000..243769a --- /dev/null +++ b/alarm_cond.c @@ -0,0 +1,198 @@ +/* + * alarm_cond.c + * + * This is an enhancement to the alarm_mutex.c program, which + * used only a mutex to synchronize access to the shared alarm + * list. This version adds a condition variable. The alarm + * thread waits on this condition variable, with a timeout that + * corresponds to the earliest timer request. If the main thread + * enters an earlier timeout, it signals the condition variable + * so that the alarm thread will wake up and process the earlier + * timeout first, requeueing the later request. + */ +#include +#include +#include "errors.h" + +/* + * The "alarm" structure now contains the time_t (time since the + * Epoch, in seconds) for each alarm, so that they can be + * sorted. Storing the requested number of seconds would not be + * enough, since the "alarm thread" cannot tell how long it has + * been on the list. + */ +typedef struct alarm_tag { + struct alarm_tag *link; + int seconds; + time_t time; /* seconds from EPOCH */ + char message[64]; +} alarm_t; + +pthread_mutex_t alarm_mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t alarm_cond = PTHREAD_COND_INITIALIZER; +alarm_t *alarm_list = NULL; +time_t current_alarm = 0; + +/* + * Insert alarm entry on list, in order. + */ +void alarm_insert (alarm_t *alarm) +{ + int status; + alarm_t **last, *next; + + /* + * LOCKING PROTOCOL: + * + * This routine requires that the caller have locked the + * alarm_mutex! + */ + last = &alarm_list; + next = *last; + while (next != NULL) { + if (next->time >= alarm->time) { + alarm->link = next; + *last = alarm; + break; + } + last = &next->link; + next = next->link; + } + /* + * If we reached the end of the list, insert the new alarm + * there. ("next" is NULL, and "last" points to the link + * field of the last item, or to the list header.) + */ + if (next == NULL) { + *last = alarm; + alarm->link = NULL; + } +#ifdef DEBUG + printf ("[list: "); + for (next = alarm_list; next != NULL; next = next->link) + printf ("%d(%d)[\"%s\"] ", next->time, + next->time - time (NULL), next->message); + printf ("]\n"); +#endif + /* + * Wake the alarm thread if it is not busy (that is, if + * current_alarm is 0, signifying that it's waiting for + * work), or if the new alarm comes before the one on + * which the alarm thread is waiting. + */ + if (current_alarm == 0 || alarm->time < current_alarm) { + current_alarm = alarm->time; + status = pthread_cond_signal (&alarm_cond); + if (status != 0) + err_abort (status, "Signal cond"); + } +} + +/* + * The alarm thread's start routine. + */ +void *alarm_thread (void *arg) +{ + alarm_t *alarm; + struct timespec cond_time; + time_t now; + int status, expired; + + /* + * Loop forever, processing commands. The alarm thread will + * be disintegrated when the process exits. Lock the mutex + * at the start -- it will be unlocked during condition + * waits, so the main thread can insert alarms. + */ + status = pthread_mutex_lock (&alarm_mutex); + if (status != 0) + err_abort (status, "Lock mutex"); + while (1) { + /* + * If the alarm list is empty, wait until an alarm is + * added. Setting current_alarm to 0 informs the insert + * routine that the thread is not busy. + */ + current_alarm = 0; + while (alarm_list == NULL) { + status = pthread_cond_wait (&alarm_cond, &alarm_mutex); + if (status != 0) + err_abort (status, "Wait on cond"); + } + alarm = alarm_list; + alarm_list = alarm->link; + now = time (NULL); + expired = 0; + if (alarm->time > now) { +#ifdef DEBUG + printf ("[waiting: %d(%d)\"%s\"]\n", alarm->time, + alarm->time - time (NULL), alarm->message); +#endif + cond_time.tv_sec = alarm->time; + cond_time.tv_nsec = 0; + current_alarm = alarm->time; + while (current_alarm == alarm->time) { + status = pthread_cond_timedwait ( + &alarm_cond, &alarm_mutex, &cond_time); + if (status == ETIMEDOUT) { + expired = 1; + break; + } + if (status != 0) + err_abort (status, "Cond timedwait"); + } + if (!expired) + alarm_insert (alarm); + } else + expired = 1; + if (expired) { + printf ("(%d) %s\n", alarm->seconds, alarm->message); + free (alarm); + } + } +} + +int main (int argc, char *argv[]) +{ + int status; + char line[128]; + alarm_t *alarm; + pthread_t thread; + + status = pthread_create ( + &thread, NULL, alarm_thread, NULL); + if (status != 0) + err_abort (status, "Create alarm thread"); + while (1) { + printf ("Alarm> "); + if (fgets (line, sizeof (line), stdin) == NULL) exit (0); + if (strlen (line) <= 1) continue; + alarm = (alarm_t*)malloc (sizeof (alarm_t)); + if (alarm == NULL) + errno_abort ("Allocate alarm"); + + /* + * Parse input line into seconds (%d) and a message + * (%64[^\n]), consisting of up to 64 characters + * separated from the seconds by whitespace. + */ + if (sscanf (line, "%d %64[^\n]", + &alarm->seconds, alarm->message) < 2) { + fprintf (stderr, "Bad command\n"); + free (alarm); + } else { + status = pthread_mutex_lock (&alarm_mutex); + if (status != 0) + err_abort (status, "Lock mutex"); + alarm->time = time (NULL) + alarm->seconds; + /* + * Insert the new alarm into the list of alarms, + * sorted by expiration time. + */ + alarm_insert (alarm); + status = pthread_mutex_unlock (&alarm_mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + } + } +} diff --git a/alarm_fork.c b/alarm_fork.c new file mode 100644 index 0000000..2f0b7e4 --- /dev/null +++ b/alarm_fork.c @@ -0,0 +1,56 @@ +/* + * alarm_fork.c + * + * This version of alarm.c uses fork to create a new process to + * wait for each alarm to expire. + */ +#include +#include +#include "errors.h" + +int main (int argc, char *argv[]) +{ + int status; + char line[128]; + int seconds; + pid_t pid; + char message[64]; + + while (1) { + printf ("Alarm> "); + if (fgets (line, sizeof (line), stdin) == NULL) exit (0); + if (strlen (line) <= 1) continue; + + /* + * Parse input line into seconds (%d) and a message + * (%64[^\n]), consisting of up to 64 characters + * separated from the seconds by whitespace. + */ + if (sscanf (line, "%d %64[^\n]", + &seconds, message) < 2) { + fprintf (stderr, "Bad command\n"); + } else { + pid = fork (); + if (pid == (pid_t)-1) + errno_abort ("Fork"); + if (pid == (pid_t)0) { + /* + * If we're in the child, wait and then print a message + */ + sleep (seconds); + printf ("(%d) %s\n", seconds, message); + exit (0); + } else { + /* + * In the parent, call waitpid() to collect any children that + * have already terminated. + */ + do { + pid = waitpid ((pid_t)-1, NULL, WNOHANG); + if (pid == (pid_t)-1) + errno_abort ("Wait for child"); + } while (pid != (pid_t)0); + } + } + } +} diff --git a/alarm_mutex.c b/alarm_mutex.c new file mode 100644 index 0000000..461bc82 --- /dev/null +++ b/alarm_mutex.c @@ -0,0 +1,175 @@ +/* + * alarm_mutex.c + * + * This is an enhancement to the alarm_thread.c program, which + * created an "alarm thread" for each alarm command. This new + * version uses a single alarm thread, which reads the next + * entry in a list. The main thread places new requests onto the + * list, in order of absolute expiration time. The list is + * protected by a mutex, and the alarm thread sleeps for at + * least 1 second, each iteration, to ensure that the main + * thread can lock the mutex to add new work to the list. + */ +#include +#include +#include "errors.h" + +/* + * The "alarm" structure now contains the time_t (time since the + * Epoch, in seconds) for each alarm, so that they can be + * sorted. Storing the requested number of seconds would not be + * enough, since the "alarm thread" cannot tell how long it has + * been on the list. + */ +typedef struct alarm_tag { + struct alarm_tag *link; + int seconds; + time_t time; /* seconds from EPOCH */ + char message[64]; +} alarm_t; + +pthread_mutex_t alarm_mutex = PTHREAD_MUTEX_INITIALIZER; +alarm_t *alarm_list = NULL; + +/* + * The alarm thread's start routine. + */ +void *alarm_thread (void *arg) +{ + alarm_t *alarm; + int sleep_time; + time_t now; + int status; + + /* + * Loop forever, processing commands. The alarm thread will + * be disintegrated when the process exits. + */ + while (1) { + status = pthread_mutex_lock (&alarm_mutex); + if (status != 0) + err_abort (status, "Lock mutex"); + alarm = alarm_list; + + /* + * If the alarm list is empty, wait for one second. This + * allows the main thread to run, and read another + * command. If the list is not empty, remove the first + * item. Compute the number of seconds to wait -- if the + * result is less than 0 (the time has passed), then set + * the sleep_time to 0. + */ + if (alarm == NULL) + sleep_time = 1; + else { + alarm_list = alarm->link; + now = time (NULL); + if (alarm->time <= now) + sleep_time = 0; + else + sleep_time = alarm->time - now; +#ifdef DEBUG + printf ("[waiting: %d(%d)\"%s\"]\n", alarm->time, + sleep_time, alarm->message); +#endif + } + + /* + * Unlock the mutex before waiting, so that the main + * thread can lock it to insert a new alarm request. If + * the sleep_time is 0, then call sched_yield, giving + * the main thread a chance to run if it has been + * readied by user input, without delaying the message + * if there's no input. + */ + status = pthread_mutex_unlock (&alarm_mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + if (sleep_time > 0) + sleep (sleep_time); + else + sched_yield (); + + /* + * If a timer expired, print the message and free the + * structure. + */ + if (alarm != NULL) { + printf ("(%d) %s\n", alarm->seconds, alarm->message); + free (alarm); + } + } +} + +int main (int argc, char *argv[]) +{ + int status; + char line[128]; + alarm_t *alarm, **last, *next; + pthread_t thread; + + status = pthread_create ( + &thread, NULL, alarm_thread, NULL); + if (status != 0) + err_abort (status, "Create alarm thread"); + while (1) { + printf ("alarm> "); + if (fgets (line, sizeof (line), stdin) == NULL) exit (0); + if (strlen (line) <= 1) continue; + alarm = (alarm_t*)malloc (sizeof (alarm_t)); + if (alarm == NULL) + errno_abort ("Allocate alarm"); + + /* + * Parse input line into seconds (%d) and a message + * (%64[^\n]), consisting of up to 64 characters + * separated from the seconds by whitespace. + */ + if (sscanf (line, "%d %64[^\n]", + &alarm->seconds, alarm->message) < 2) { + fprintf (stderr, "Bad command\n"); + free (alarm); + } else { + status = pthread_mutex_lock (&alarm_mutex); + if (status != 0) + err_abort (status, "Lock mutex"); + alarm->time = time (NULL) + alarm->seconds; + + /* + * Insert the new alarm into the list of alarms, + * sorted by expiration time. + */ + last = &alarm_list; + next = *last; + while (next != NULL) { + if (next->time >= alarm->time) { + alarm->link = next; + *last = alarm; + break; + } + last = &next->link; + next = next->link; + } + /* + * If we reached the end of the list, insert the new + * alarm there. ("next" is NULL, and "last" points + * to the link field of the last item, or to the + * list header). + */ + if (next == NULL) { + *last = alarm; + alarm->link = NULL; + } +#ifdef DEBUG + printf ("[list: "); + for (next = alarm_list; next != NULL; next = next->link) + printf ("%d(%d)[\"%s\"] ", next->time, + next->time - time (NULL), next->message); + printf ("]\n"); +#endif + status = pthread_mutex_unlock (&alarm_mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + } + } +} diff --git a/alarm_thread.c b/alarm_thread.c new file mode 100644 index 0000000..9dbc05f --- /dev/null +++ b/alarm_thread.c @@ -0,0 +1,59 @@ +/* + * alarm_fork.c + * + * This version of alarm.c uses pthread_create to create a + * separate thread to wait for each alarm to expire. + */ +#include +#include "errors.h" + +typedef struct alarm_tag { + int seconds; + char message[64]; +} alarm_t; + +void *alarm_thread (void *arg) +{ + alarm_t *alarm = (alarm_t*)arg; + int status; + + status = pthread_detach (pthread_self ()); + if (status != 0) + err_abort (status, "Detach thread"); + sleep (alarm->seconds); + printf ("(%d) %s\n", alarm->seconds, alarm->message); + free (alarm); + return NULL; +} +int main (int argc, char *argv[]) +{ + int status; + char line[128]; + alarm_t *alarm; + pthread_t thread; + + while (1) { + printf ("Alarm> "); + if (fgets (line, sizeof (line), stdin) == NULL) exit (0); + if (strlen (line) <= 1) continue; + alarm = (alarm_t*)malloc (sizeof (alarm_t)); + if (alarm == NULL) + errno_abort ("Allocate alarm"); + + /* + * Parse input line into seconds (%d) and a message + * (%64[^\n]), consisting of up to 64 characters + * separated from the seconds by whitespace. + */ + if (sscanf (line, "%d %64[^\n]", + &alarm->seconds, alarm->message) < 2) { + fprintf (stderr, "Bad command\n"); + free (alarm); + } else { + status = pthread_create ( + &thread, NULL, alarm_thread, alarm); + if (status != 0) + err_abort (status, "Create alarm thread"); + } + } +} diff --git a/atfork.c b/atfork.c new file mode 100644 index 0000000..2e36594 --- /dev/null +++ b/atfork.c @@ -0,0 +1,130 @@ +/* + * atfork.c + * + * Demonstrate the use of "fork handlers" to protect data + * invariants across a fork. + */ +#include +#include +#include +#include "errors.h" + +pid_t self_pid; /* pid of current process */ +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +/* + * This routine will be called prior to executing the fork, + * within the parent process. + */ +void fork_prepare (void) +{ + int status; + + /* + * Lock the mutex in the parent before creating the child, + * to ensure that no other thread can lock it (or change any + * associated shared state) until after the fork completes. + */ + status = pthread_mutex_lock (&mutex); + if (status != 0) + err_abort (status, "Lock in prepare handler"); +} + +/* + * This routine will be called after executing the fork, within + * the parent process + */ +void fork_parent (void) +{ + int status; + + /* + * Unlock the mutex in the parent after the child has been created. + */ + status = pthread_mutex_unlock (&mutex); + if (status != 0) + err_abort (status, "Unlock in parent handler"); +} + +/* + * This routine will be called after executing the fork, within + * the child process + */ +void fork_child (void) +{ + int status; + + /* + * Update the file scope "self_pid" within the child process, and unlock + * the mutex. + */ + self_pid = getpid (); + status = pthread_mutex_unlock (&mutex); + if (status != 0) + err_abort (status, "Unlock in child handler"); +} + +/* + * Thread start routine, which will fork a new child process. + */ +void *thread_routine (void *arg) +{ + pid_t child_pid; + int status; + + child_pid = fork (); + if (child_pid == (pid_t)-1) + errno_abort ("Fork"); + + /* + * Lock the mutex -- without the atfork handlers, the mutex will remain + * locked in the child process and this lock attempt will hang (or fail + * with EDEADLK) in the child. + */ + status = pthread_mutex_lock (&mutex); + if (status != 0) + err_abort (status, "Lock in child"); + status = pthread_mutex_unlock (&mutex); + if (status != 0) + err_abort (status, "Unlock in child"); + printf ("After fork: %d (%d)\n", child_pid, self_pid); + if (child_pid != 0) { + if ((pid_t)-1 == waitpid (child_pid, (int*)0, 0)) + errno_abort ("Wait for child"); + } + return NULL; +} + +int main (int argc, char *argv[]) +{ + pthread_t fork_thread; + int atfork_flag = 1; + int status; + + if (argc > 1) + atfork_flag = atoi (argv[1]); + if (atfork_flag) { + status = pthread_atfork (fork_prepare, fork_parent, fork_child); + if (status != 0) + err_abort (status, "Register fork handlers"); + } + self_pid = getpid (); + status = pthread_mutex_lock (&mutex); + if (status != 0) + err_abort (status, "Lock mutex"); + /* + * Create a thread while the mutex is locked. It will fork a process, + * which (without atfork handlers) will run with the mutex locked. + */ + status = pthread_create (&fork_thread, NULL, thread_routine, NULL); + if (status != 0) + err_abort (status, "Create thread"); + sleep (5); + status = pthread_mutex_unlock (&mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + status = pthread_join (fork_thread, NULL); + if (status != 0) + err_abort (status, "Join thread"); + return 0; +} diff --git a/backoff.c b/backoff.c new file mode 100644 index 0000000..32438a3 --- /dev/null +++ b/backoff.c @@ -0,0 +1,199 @@ +/* + * backoff.c + * + * Demonstrate deadlock avoidance using "mutex backoff". + * + * Special notes: On a Solaris 2.5 uniprocessor, this test will + * not produce interleaved output unless extra LWPs are created + * by calling thr_setconcurrency(), because threads are not + * timesliced. + */ +#include +#include +#include "errors.h" + +#define ITERATIONS 10 + +/* + * Initialize a static array of 3 mutexes. + */ +pthread_mutex_t mutex[3] = { + PTHREAD_MUTEX_INITIALIZER, + PTHREAD_MUTEX_INITIALIZER, + PTHREAD_MUTEX_INITIALIZER + }; + +int backoff = 1; /* Whether to backoff or deadlock */ +int yield_flag = 0; /* 0: no yield, >0: yield, <0: sleep */ + +/* + * This is a thread start routine that locks all mutexes in + * order, to ensure a conflict with lock_reverse, which does the + * opposite. + */ +void *lock_forward (void *arg) +{ + int i, iterate, backoffs; + int status; + + for (iterate = 0; iterate < ITERATIONS; iterate++) { + backoffs = 0; + for (i = 0; i < 3; i++) { + if (i == 0) { + status = pthread_mutex_lock (&mutex[i]); + if (status != 0) + err_abort (status, "First lock"); + } else { + if (backoff) + status = pthread_mutex_trylock (&mutex[i]); + else + status = pthread_mutex_lock (&mutex[i]); + if (status == EBUSY) { + backoffs++; + DPRINTF (( + " [forward locker backing off at %d]\n", + i)); + for (; i >= 0; i--) { + status = pthread_mutex_unlock (&mutex[i]); + if (status != 0) + err_abort (status, "Backoff"); + } + } else { + if (status != 0) + err_abort (status, "Lock mutex"); + DPRINTF ((" forward locker got %d\n", i)); + } + } + /* + * Yield processor, if needed to be sure locks get + * interleaved on a uniprocessor. + */ + if (yield_flag) { + if (yield_flag > 0) + sched_yield (); + else + sleep (1); + } + } + /* + * Report that we got 'em, and unlock to try again. + */ + printf ( + "lock forward got all locks, %d backoffs\n", backoffs); + pthread_mutex_unlock (&mutex[2]); + pthread_mutex_unlock (&mutex[1]); + pthread_mutex_unlock (&mutex[0]); + sched_yield (); + } + return NULL; +} + +/* + * This is a thread start routine that locks all mutexes in + * reverse order, to ensure a conflict with lock_forward, which + * does the opposite. + */ +void *lock_backward (void *arg) +{ + int i, iterate, backoffs; + int status; + + for (iterate = 0; iterate < ITERATIONS; iterate++) { + backoffs = 0; + for (i = 2; i >= 0; i--) { + if (i == 2) { + status = pthread_mutex_lock (&mutex[i]); + if (status != 0) + err_abort (status, "First lock"); + } else { + if (backoff) + status = pthread_mutex_trylock (&mutex[i]); + else + status = pthread_mutex_lock (&mutex[i]); + if (status == EBUSY) { + backoffs++; + DPRINTF (( + " [backward locker backing off at %d]\n", + i)); + for (; i < 3; i++) { + status = pthread_mutex_unlock (&mutex[i]); + if (status != 0) + err_abort (status, "Backoff"); + } + } else { + if (status != 0) + err_abort (status, "Lock mutex"); + DPRINTF ((" backward locker got %d\n", i)); + } + } + /* + * Yield processor, if needed to be sure locks get + * interleaved on a uniprocessor. + */ + if (yield_flag) { + if (yield_flag > 0) + sched_yield (); + else + sleep (1); + } + } + /* + * Report that we got 'em, and unlock to try again. + */ + printf ( + "lock backward got all locks, %d backoffs\n", backoffs); + pthread_mutex_unlock (&mutex[0]); + pthread_mutex_unlock (&mutex[1]); + pthread_mutex_unlock (&mutex[2]); + sched_yield (); + } + return NULL; +} + +int main (int argc, char *argv[]) +{ + pthread_t forward, backward; + int status; + +#ifdef sun + /* + * On Solaris 2.5, threads are not timesliced. To ensure + * that our threads can run concurrently, we need to + * increase the concurrency level. + */ + DPRINTF (("Setting concurrency level to 2\n")); + thr_setconcurrency (2); +#endif + + /* + * If the first argument is absent, or nonzero, a backoff + * algorithm will be used to avoid deadlock. If the first + * argument is zero, the program will deadlock on a lock + * "collision." + */ + if (argc > 1) + backoff = atoi (argv[1]); + + /* + * If the second argument is absent, or zero, the two + * threads run "at speed." On some systems, especially + * uniprocessors, one thread may complete before the other + * has a chance to run, and you won't see a deadlock or + * backoffs. In that case, try running with the argument set + * to a positive number to cause the threads to call + * sched_yield() at each lock; or, to make it even more + * obvious, set to a negative number to cause the threads to + * call sleep(1) instead. + */ + if (argc > 2) + yield_flag = atoi (argv[2]); + status = pthread_create ( + &forward, NULL, lock_forward, NULL); + if (status != 0) + err_abort (status, "Create forward"); + status = pthread_create ( + &backward, NULL, lock_backward, NULL); + if (status != 0) + err_abort (status, "Create backward"); + pthread_exit (NULL); +} diff --git a/barrier.c b/barrier.c new file mode 100644 index 0000000..06cba29 --- /dev/null +++ b/barrier.c @@ -0,0 +1,144 @@ +/* + * barrier.c + * + * This file implements the "barrier" synchronization construct. + * + * A barrier causes threads to wait until a set of threads has + * all "reached" the barrier. The number of threads required is + * set when the barrier is initialized, and cannot be changed + * except by reinitializing. + * + * The barrier_init() and barrier_destroy() functions, + * respectively, allow you to initialize and destroy the + * barrier. + * + * The barrier_wait() function allows a thread to wait for a + * barrier to be completed. One thread (the one that happens to + * arrive last) will return from barrier_wait() with the status + * -1 on success -- others will return with 0. The special + * status makes it easy for the calling code to cause one thread + * to do something in a serial region before entering another + * parallel section of code. + */ +#include +#include "errors.h" +#include "barrier.h" + +/* + * Initialize a barrier for use. + */ +int barrier_init (barrier_t *barrier, int count) +{ + int status; + + barrier->threshold = barrier->counter = count; + barrier->cycle = 0; + status = pthread_mutex_init (&barrier->mutex, NULL); + if (status != 0) + return status; + status = pthread_cond_init (&barrier->cv, NULL); + if (status != 0) { + pthread_mutex_destroy (&barrier->mutex); + return status; + } + barrier->valid = BARRIER_VALID; + return 0; +} + +/* + * Destroy a barrier when done using it. + */ +int barrier_destroy (barrier_t *barrier) +{ + int status, status2; + + if (barrier->valid != BARRIER_VALID) + return EINVAL; + + status = pthread_mutex_lock (&barrier->mutex); + if (status != 0) + return status; + + /* + * Check whether any threads are known to be waiting; report + * "BUSY" if so. + */ + if (barrier->counter != barrier->threshold) { + pthread_mutex_unlock (&barrier->mutex); + return EBUSY; + } + + barrier->valid = 0; + status = pthread_mutex_unlock (&barrier->mutex); + if (status != 0) + return status; + + /* + * If unable to destroy either 1003.1c synchronization + * object, return the error status. + */ + status = pthread_mutex_destroy (&barrier->mutex); + status2 = pthread_cond_destroy (&barrier->cv); + return (status == 0 ? status : status2); +} + +/* + * Wait for all members of a barrier to reach the barrier. When + * the count (of remaining members) reaches 0, broadcast to wake + * all threads waiting. + */ +int barrier_wait (barrier_t *barrier) +{ + int status, cancel, tmp, cycle; + + if (barrier->valid != BARRIER_VALID) + return EINVAL; + + status = pthread_mutex_lock (&barrier->mutex); + if (status != 0) + return status; + + cycle = barrier->cycle; /* Remember which cycle we're on */ + + if (--barrier->counter == 0) { + barrier->cycle = !barrier->cycle; + barrier->counter = barrier->threshold; + status = pthread_cond_broadcast (&barrier->cv); + /* + * The last thread into the barrier will return status + * -1 rather than 0, so that it can be used to perform + * some special serial code following the barrier. + */ + if (status == 0) + status = -1; + } else { + /* + * Wait with cancellation disabled, because barrier_wait + * should not be a cancellation point. + */ + pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &cancel); + + /* + * Wait until the barrier's cycle changes, which means + * that it has been broadcast, and we don't want to wait + * anymore. + */ + while (cycle == barrier->cycle) { + status = pthread_cond_wait ( + &barrier->cv, &barrier->mutex); + if (status != 0) break; + } + + pthread_setcancelstate (cancel, &tmp); + } + /* + * Ignore an error in unlocking. It shouldn't happen, and + * reporting it here would be misleading -- the barrier wait + * completed, after all, whereas returning, for example, + * EINVAL would imply the wait had failed. The next attempt + * to use the barrier *will* return an error, or hang, due + * to whatever happened to the mutex. + */ + pthread_mutex_unlock (&barrier->mutex); + return status; /* error, -1 for waker, or 0 */ +} diff --git a/barrier.h b/barrier.h new file mode 100644 index 0000000..4e3fa21 --- /dev/null +++ b/barrier.h @@ -0,0 +1,42 @@ +/* + * barrier.h + * + * This header file describes the "barrier" synchronization + * construct. The type barrier_t describes the full state of the + * barrier including the POSIX 1003.1c synchronization objects + * necessary. + * + * A barrier causes threads to wait until a set of threads has + * all "reached" the barrier. The number of threads required is + * set when the barrier is initialized, and cannot be changed + * except by reinitializing. + */ +#include + +/* + * Structure describing a barrier. + */ +typedef struct barrier_tag { + pthread_mutex_t mutex; /* Control access to barrier */ + pthread_cond_t cv; /* wait for barrier */ + int valid; /* set when valid */ + int threshold; /* number of threads required */ + int counter; /* current number of threads */ + int cycle; /* alternate wait cycles (0 or 1) */ +} barrier_t; + +#define BARRIER_VALID 0xdbcafe + +/* + * Support static initialization of barriers + */ +#define BARRIER_INITIALIZER(cnt) \ + {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, \ + BARRIER_VALID, cnt, cnt, 0} + +/* + * Define barrier functions + */ +extern int barrier_init (barrier_t *barrier, int count); +extern int barrier_destroy (barrier_t *barrier); +extern int barrier_wait (barrier_t *barrier); diff --git a/barrier_main.c b/barrier_main.c new file mode 100644 index 0000000..eb86abd --- /dev/null +++ b/barrier_main.c @@ -0,0 +1,115 @@ +/* + * barrier_main.c + * + * Demonstrate use of barriers, using the barrier implementation + * in barrier.c. + */ +#include +#include "barrier.h" +#include "errors.h" + +#define THREADS 5 +#define ARRAY 6 +#define INLOOPS 1000 +#define OUTLOOPS 10 + +/* + * Keep track of each thread + */ +typedef struct thread_tag { + pthread_t thread_id; + int number; + int increment; + int array[ARRAY]; + } thread_t; + +barrier_t barrier; +thread_t thread[THREADS]; + +/* + * Start routine for threads. + */ +void *thread_routine (void *arg) +{ + thread_t *self = (thread_t*)arg; /* Thread's thread_t */ + int in_loop, out_loop, count, status; + + /* + * Loop through OUTLOOPS barrier cycles. + */ + for (out_loop = 0; out_loop < OUTLOOPS; out_loop++) { + status = barrier_wait (&barrier); + if (status > 0) + err_abort (status, "Wait on barrier"); + + /* + * This inner loop just adds a value to each element in + * the working array. + */ + for (in_loop = 0; in_loop < INLOOPS; in_loop++) + for (count = 0; count < ARRAY; count++) + self->array[count] += self->increment; + + status = barrier_wait (&barrier); + if (status > 0) + err_abort (status, "Wait on barrier"); + + /* + * The barrier causes one thread to return with the + * special return status -1. The thread receiving this + * value increments each element in the shared array. + */ + if (status == -1) { + int thread_num; + + for (thread_num = 0; thread_num < THREADS; thread_num++) + thread[thread_num].increment += 1; + } + } + return NULL; +} + +int main (int arg, char *argv[]) +{ + int thread_count, array_count; + int status; + + barrier_init (&barrier, THREADS); + + /* + * Create a set of threads that will use the barrier. + */ + for (thread_count = 0; thread_count < THREADS; thread_count++) { + thread[thread_count].increment = thread_count; + thread[thread_count].number = thread_count; + + for (array_count = 0; array_count < ARRAY; array_count++) + thread[thread_count].array[array_count] = array_count + 1; + + status = pthread_create (&thread[thread_count].thread_id, + NULL, thread_routine, (void*)&thread[thread_count]); + if (status != 0) + err_abort (status, "Create thread"); + } + + /* + * Now join with each of the threads. + */ + for (thread_count = 0; thread_count < THREADS; thread_count++) { + status = pthread_join (thread[thread_count].thread_id, NULL); + if (status != 0) + err_abort (status, "Join thread"); + + printf ("%02d: (%d) ", thread_count, thread[thread_count].increment); + + for (array_count = 0; array_count < ARRAY; array_count++) + printf ("%010u ", thread[thread_count].array[array_count]); + printf ("\n"); + } + + /* + * To be thorough, destroy the barrier. + */ + barrier_destroy (&barrier); + return 0; +} diff --git a/cancel.c b/cancel.c new file mode 100644 index 0000000..8e07090 --- /dev/null +++ b/cancel.c @@ -0,0 +1,66 @@ +/* + * cancel.c + * + * Demonstrate use of synchronous cancellation using + * pthread_testcancel. + * + * Special notes: On a Solaris 2.5 uniprocessor, this test will + * hang unless a second LWP is created by calling + * thr_setconcurrency() because threads are not timesliced. + */ +#include +#include "errors.h" + +static int counter; + +/* + * Loop until cancelled. The thread can be cancelled only + * when it calls pthread_testcancel, which it does each 1000 + * iterations. + */ +void *thread_routine (void *arg) +{ + DPRINTF (("thread_routine starting\n")); + for (counter = 0; ; counter++) + if ((counter % 1000) == 0) { + DPRINTF (("calling testcancel\n")); + pthread_testcancel (); + } +} + +int main (int argc, char *argv[]) +{ + pthread_t thread_id; + void *result; + int status; + +#ifdef sun + /* + * On Solaris 2.5, threads are not timesliced. To ensure + * that our two threads can run concurrently, we need to + * increase the concurrency level to 2. + */ + DPRINTF (("Setting concurrency level to 2\n")); + thr_setconcurrency (2); +#endif + status = pthread_create ( + &thread_id, NULL, thread_routine, NULL); + if (status != 0) + err_abort (status, "Create thread"); + sleep (2); + + DPRINTF (("calling cancel\n")); + status = pthread_cancel (thread_id); + if (status != 0) + err_abort (status, "Cancel thread"); + + DPRINTF (("calling join\n")); + status = pthread_join (thread_id, &result); + if (status != 0) + err_abort (status, "Join thread"); + if (result == PTHREAD_CANCELED) + printf ("Thread cancelled at iteration %d\n", counter); + else + printf ("Thread was not cancelled\n"); + return 0; +} diff --git a/cancel_async.c b/cancel_async.c new file mode 100644 index 0000000..a808ea5 --- /dev/null +++ b/cancel_async.c @@ -0,0 +1,130 @@ +/* + * cancel_async.c + * + * Demonstrate asynchronous cancellation of a compute-bound + * thread. + * + * Special notes: On a Solaris 2.5 uniprocessor, this test will + * hang unless a second LWP is created by calling + * thr_setconcurrency() because threads are not timesliced. + */ +#include +#include "errors.h" + +#define SIZE 10 /* array size */ + +static int matrixa[SIZE][SIZE]; +static int matrixb[SIZE][SIZE]; +static int matrixc[SIZE][SIZE]; + +#ifdef DEBUG +void print_array (int matrix[SIZE][SIZE]) +{ + int i, j; + int first; + + for (i = 0; i < SIZE; i++) { + printf ("["); + first = 1; + for (j = 0; j < SIZE; j++) { + if (!first) + printf (","); + printf ("%x", matrix[i][j]); + first = 0; + } + printf ("]\n"); + } + +} +#endif + +/* + * Loop until cancelled. The thread can be cancelled at any + * point within the inner loop, where asynchronous cancellation + * is enabled. The loop multiplies the two matrices matrixa + * and matrixb. + */ +void *thread_routine (void *arg) +{ + int cancel_type, status; + int i, j, k, value = 1; + + /* + * Initialize the matrices to something arbitrary. + */ + for (i = 0; i < SIZE; i++) + for (j = 0; j < SIZE; j++) { + matrixa[i][j] = i; + matrixb[i][j] = j; + } + + while (1) { + /* + * Compute the matrix product of matrixa and matrixb. + */ + status = pthread_setcanceltype ( + PTHREAD_CANCEL_ASYNCHRONOUS, + &cancel_type); + if (status != 0) + err_abort (status, "Set cancel type"); + for (i = 0; i < SIZE; i++) + for (j = 0; j < SIZE; j++) { + matrixc[i][j] = 0; + for (k = 0; k < SIZE; k++) + matrixc[i][j] += matrixa[i][k] * matrixb[k][j]; + } + status = pthread_setcanceltype ( + cancel_type, + &cancel_type); + if (status != 0) + err_abort (status, "Set cancel type"); + + /* + * Copy the result (matrixc) into matrixa to start again + */ + for (i = 0; i < SIZE; i++) + for (j = 0; j < SIZE; j++) + matrixa[i][j] = matrixc[i][j]; + } +} + +int main (int argc, char *argv[]) +{ + pthread_t thread_id; + void *result; + int status; + +#ifdef sun + /* + * On Solaris 2.5, threads are not timesliced. To ensure + * that our two threads can run concurrently, we need to + * increase the concurrency level to 2. + */ + DPRINTF (("Setting concurrency level to 2\n")); + thr_setconcurrency (2); +#endif + status = pthread_create ( + &thread_id, NULL, thread_routine, NULL); + if (status != 0) + err_abort (status, "Create thread"); + sleep (1); + status = pthread_cancel (thread_id); + if (status != 0) + err_abort (status, "Cancel thread"); + status = pthread_join (thread_id, &result); + if (status != 0) + err_abort (status, "Join thread"); + if (result == PTHREAD_CANCELED) + printf ("Thread cancelled\n"); + else + printf ("Thread was not cancelled\n"); +#ifdef DEBUG + printf ("Matrix a:\n"); + print_array (matrixa); + printf ("\nMatrix b:\n"); + print_array (matrixb); + printf ("\nMatrix c:\n"); + print_array (matrixc); +#endif + return 0; +} diff --git a/cancel_cleanup.c b/cancel_cleanup.c new file mode 100644 index 0000000..b7c6220 --- /dev/null +++ b/cancel_cleanup.c @@ -0,0 +1,100 @@ +/* + * cancel_cleanup.c + * + * Demonstrate use of a cleanup handler to release resources and + * restore invariants on cancellation of a wait. + */ +#include +#include "errors.h" + +#define THREADS 5 + +/* + * Control structure shared by the test threads, containing + * the synchronization and invariant data. + */ +typedef struct control_tag { + int counter, busy; + pthread_mutex_t mutex; + pthread_cond_t cv; +} control_t; + +control_t control = + {0, 1, PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER}; + +/* + * This routine is installed as the cancellation cleanup + * handler around the cancellable condition wait. It will + * be called by the system when the thread is cancelled. + */ +void cleanup_handler (void *arg) +{ + control_t *st = (control_t *)arg; + int status; + + st->counter--; + printf ("cleanup_handler: counter == %d\n", st->counter); + status = pthread_mutex_unlock (&st->mutex); + if (status != 0) + err_abort (status, "Unlock in cleanup handler"); +} + +/* + * Multiple threads are created running this routine (controlled + * by the THREADS macro). They maintain a "counter" invariant, + * which expresses the number of running threads. They specify a + * nonzero value to pthread_cleanup_pop to run the same + * "finalization" action when cancellation does not occur. + */ +void *thread_routine (void *arg) +{ + int status; + + pthread_cleanup_push (cleanup_handler, (void*)&control); + + status = pthread_mutex_lock (&control.mutex); + if (status != 0) + err_abort (status, "Mutex lock"); + control.counter++; + + while (control.busy) { + status = pthread_cond_wait (&control.cv, &control.mutex); + if (status != 0) + err_abort (status, "Wait on condition"); + } + + pthread_cleanup_pop (1); + return NULL; +} + +int main (int argc, char *argv[]) +{ + pthread_t thread_id[THREADS]; + int count; + void *result; + int status; + + for (count = 0; count < THREADS; count++) { + status = pthread_create ( + &thread_id[count], NULL, thread_routine, NULL); + if (status != 0) + err_abort (status, "Create thread"); + } + + sleep (2); + + for (count = 0; count < THREADS; count++) { + status = pthread_cancel (thread_id[count]); + if (status != 0) + err_abort (status, "Cancel thread"); + + status = pthread_join (thread_id[count], &result); + if (status != 0) + err_abort (status, "Join thread"); + if (result == PTHREAD_CANCELED) + printf ("thread %d cancelled\n", count); + else + printf ("thread %d was not cancelled\n", count); + } + return 0; +} diff --git a/cancel_disable.c b/cancel_disable.c new file mode 100644 index 0000000..5f473fe --- /dev/null +++ b/cancel_disable.c @@ -0,0 +1,68 @@ +/* + * cancel_disable.c + * + * Demonstrate running a section of code with cancellation + * disabled. + */ +#include +#include "errors.h" + +static int counter; + +/* + * Thread start routine. + */ +void *thread_routine (void *arg) +{ + int state; + int status; + + for (counter = 0; ; counter++) { + + /* + * Each 755 iterations, disable cancellation and sleep + * for one second. + * + * Each 1000 iterations, test for a pending cancel by + * calling pthread_testcancel(). + */ + if ((counter % 755) == 0) { + status = pthread_setcancelstate ( + PTHREAD_CANCEL_DISABLE, &state); + if (status != 0) + err_abort (status, "Disable cancel"); + sleep (1); + status = pthread_setcancelstate ( + state, &state); + if (status != 0) + err_abort (status, "Restore cancel"); + } else + if ((counter % 1000) == 0) + pthread_testcancel (); + } +} + +int main (int argc, char *argv[]) +{ + pthread_t thread_id; + void *result; + int status; + + status = pthread_create ( + &thread_id, NULL, thread_routine, NULL); + if (status != 0) + err_abort (status, "Create thread"); + sleep (2); + status = pthread_cancel (thread_id); + if (status != 0) + err_abort (status, "Cancel thread"); + + status = pthread_join (thread_id, &result); + if (status != 0) + err_abort (status, "Join thread"); + if (result == PTHREAD_CANCELED) + printf ("Thread cancelled at iteration %d\n", counter); + else + printf ("Thread was not cancelled\n"); + return 0; +} diff --git a/cancel_subcontract.c b/cancel_subcontract.c new file mode 100644 index 0000000..bc3294f --- /dev/null +++ b/cancel_subcontract.c @@ -0,0 +1,115 @@ +/* + * cancel_subcontract.c + * + * Demonstrate how a thread can handle cancellation and in turn + * cancel a set of worker ("subcontractor") threads. + * + * Special notes: On a Solaris 2.5 uniprocessor, this test will + * hang unless an LWP is created for each worker thread by + * calling thr_setconcurrency(), because threads are not + * timesliced. + */ +#include +#include "errors.h" + +#define THREADS 5 + +/* + * Structure that defines the threads in a "team". + */ +typedef struct team_tag { + int join_i; /* join index */ + pthread_t workers[THREADS]; /* thread identifiers */ +} team_t; + +/* + * Start routine for worker threads. They loop waiting for a + * cancellation request. + */ +void *worker_routine (void *arg) +{ + int counter; + + for (counter = 0; ; counter++) + if ((counter % 1000) == 0) + pthread_testcancel (); +} + +/* + * Cancellation cleanup handler for the contractor thread. It + * will cancel and detach each worker in the team. + */ +void cleanup (void *arg) +{ + team_t *team = (team_t *)arg; + int count, status; + + for (count = team->join_i; count < THREADS; count++) { + status = pthread_cancel (team->workers[count]); + if (status != 0) + err_abort (status, "Cancel worker"); + + status = pthread_detach (team->workers[count]); + if (status != 0) + err_abort (status, "Detach worker"); + printf ("Cleanup: cancelled %d\n", count); + } +} + +/* + * Thread start routine for the contractor. It creates a team of + * worker threads, and then joins with them. When cancelled, the + * cleanup handler will cancel and detach the remaining threads. + */ +void *thread_routine (void *arg) +{ + team_t team; /* team info */ + int count; + void *result; /* Return status */ + int status; + + for (count = 0; count < THREADS; count++) { + status = pthread_create ( + &team.workers[count], NULL, worker_routine, NULL); + if (status != 0) + err_abort (status, "Create worker"); + } + pthread_cleanup_push (cleanup, (void*)&team); + + for (team.join_i = 0; team.join_i < THREADS; team.join_i++) { + status = pthread_join (team.workers[team.join_i], &result); + if (status != 0) + err_abort (status, "Join worker"); + } + + pthread_cleanup_pop (0); + return NULL; +} + +int main (int argc, char *argv[]) +{ + pthread_t thread_id; + int status; + +#ifdef sun + /* + * On Solaris 2.5, threads are not timesliced. To ensure + * that our threads can run concurrently, we need to + * increase the concurrency level to at least 2 plus THREADS + * (the number of workers). + */ + DPRINTF (("Setting concurrency level to %d\n", THREADS+2)); + thr_setconcurrency (THREADS+2); +#endif + status = pthread_create (&thread_id, NULL, thread_routine, NULL); + if (status != 0) + err_abort (status, "Create team"); + sleep (5); + printf ("Cancelling...\n"); + status = pthread_cancel (thread_id); + if (status != 0) + err_abort (status, "Cancel team"); + status = pthread_join (thread_id, NULL); + if (status != 0) + err_abort (status, "Join team"); +} diff --git a/cond.c b/cond.c new file mode 100644 index 0000000..6ffbc0f --- /dev/null +++ b/cond.c @@ -0,0 +1,95 @@ +/* + * cond.c + * + * Demonstrate a simple condition variable wait. + */ +#include +#include +#include "errors.h" + +typedef struct my_struct_tag { + pthread_mutex_t mutex; /* Protects access to value */ + pthread_cond_t cond; /* Signals change to value */ + int value; /* Access protected by mutex */ +} my_struct_t; + +my_struct_t data = { + PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0}; + +int hibernation = 1; /* Default to 1 second */ + +/* + * Thread start routine. It will set the main thread's predicate + * and signal the condition variable. + */ +void * +wait_thread (void *arg) +{ + int status; + + sleep (hibernation); + status = pthread_mutex_lock (&data.mutex); + if (status != 0) + err_abort (status, "Lock mutex"); + data.value = 1; /* Set predicate */ + status = pthread_cond_signal (&data.cond); + if (status != 0) + err_abort (status, "Signal condition"); + status = pthread_mutex_unlock (&data.mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + return NULL; +} + +int main (int argc, char *argv[]) +{ + int status; + pthread_t wait_thread_id; + struct timespec timeout; + + /* + * If an argument is specified, interpret it as the number + * of seconds for wait_thread to sleep before signaling the + * condition variable. You can play with this to see the + * condition wait below time out or wake normally. + */ + if (argc > 1) + hibernation = atoi (argv[1]); + + /* + * Create wait_thread. + */ + status = pthread_create (&wait_thread_id, NULL, wait_thread, NULL); + if (status != 0) + err_abort (status, "Create wait thread"); + + /* + * Wait on the condition variable for 2 seconds, or until + * signaled by the wait_thread. Normally, wait_thread + * should signal. If you raise "hibernation" above 2 + * seconds, it will time out. + */ + timeout.tv_sec = time (NULL) + 2; + timeout.tv_nsec = 0; + status = pthread_mutex_lock (&data.mutex); + if (status != 0) + err_abort (status, "Lock mutex"); + + while (data.value == 0) { + status = pthread_cond_timedwait ( + &data.cond, &data.mutex, &timeout); + if (status == ETIMEDOUT) { + printf ("Condition wait timed out.\n"); + break; + } + else if (status != 0) + err_abort (status, "Wait on condition"); + } + + if (data.value != 0) + printf ("Condition was signaled.\n"); + status = pthread_mutex_unlock (&data.mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + return 0; +} diff --git a/cond_attr.c b/cond_attr.c new file mode 100644 index 0000000..c96e5ce --- /dev/null +++ b/cond_attr.c @@ -0,0 +1,34 @@ +/* + * cond_attr.c + * + * main() creates a condition variable using a non-default attributes object, + * cond_attr. If the implementation supports the pshared attribute, the + * condition variable is created "process private". (Note that, to create a + * "process shared" condition variable, the pthread_cond_t itself must be + * placed in shared memory that is accessible to all threads using the + * condition variable.) + */ +#include +#include "errors.h" + +pthread_cond_t cond; + +int main (int argc, char *argv[]) +{ + pthread_condattr_t cond_attr; + int status; + + status = pthread_condattr_init (&cond_attr); + if (status != 0) + err_abort (status, "Create attr"); +#ifdef _POSIX_THREAD_PROCESS_SHARED + status = pthread_condattr_setpshared ( + &cond_attr, PTHREAD_PROCESS_PRIVATE); + if (status != 0) + err_abort (status, "Set pshared"); +#endif + status = pthread_cond_init (&cond, &cond_attr); + if (status != 0) + err_abort (status, "Init cond"); + return 0; +} diff --git a/cond_dynamic.c b/cond_dynamic.c new file mode 100644 index 0000000..49a8be1 --- /dev/null +++ b/cond_dynamic.c @@ -0,0 +1,40 @@ +/* + * cond_dynamic.c + * + * Demonstrate dynamic initialization of a condition variable. + */ +#include +#include "errors.h" + +/* + * Define a structure, with a mutex and condition variable. + */ +typedef struct my_struct_tag { + pthread_mutex_t mutex; /* Protects access to value */ + pthread_cond_t cond; /* Signals change to value */ + int value; /* Access protected by mutex */ +} my_struct_t; + +int main (int argc, char *argv[]) +{ + my_struct_t *data; + int status; + + data = malloc (sizeof (my_struct_t)); + if (data == NULL) + errno_abort ("Allocate structure"); + status = pthread_mutex_init (&data->mutex, NULL); + if (status != 0) + err_abort (status, "Init mutex"); + status = pthread_cond_init (&data->cond, NULL); + if (status != 0) + err_abort (status, "Init condition"); + status = pthread_cond_destroy (&data->cond); + if (status != 0) + err_abort (status, "Destroy condition"); + status = pthread_mutex_destroy (&data->mutex); + if (status != 0) + err_abort (status, "Destroy mutex"); + (void)free (data); + return status; +} diff --git a/cond_static.c b/cond_static.c new file mode 100644 index 0000000..1da6cfd --- /dev/null +++ b/cond_static.c @@ -0,0 +1,27 @@ +/* + * cond_static.c + * + * Demonstrate static initialization of a condition variable. + */ +#include +#include "errors.h" + +/* + * Declare a structure, with a mutex and condition variable, + * statically initialized. This is the same as using + * pthread_mutex_init and pthread_cond_init, with the default + * attributes. + */ +typedef struct my_struct_tag { + pthread_mutex_t mutex; /* Protects access to value */ + pthread_cond_t cond; /* Signals change to value */ + int value; /* Access protected by mutex */ +} my_struct_t; + +my_struct_t data = { + PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0}; + +int main (int argc, char *argv[]) +{ + return 0; +} diff --git a/crew.c b/crew.c new file mode 100644 index 0000000..be4564f --- /dev/null +++ b/crew.c @@ -0,0 +1,485 @@ +/* + * crew.c + * + * Demonstrate a work crew implementing a simple parallel search + * through a directory tree. + * + * Special notes: On a Solaris 2.5 uniprocessor, this test will + * not produce interleaved output unless extra LWPs are created + * by calling thr_setconcurrency(), because threads are not + * timesliced. + */ +#include +#include +#include +#include +#include "errors.h" + +#define CREW_SIZE 4 + +/* + * Queued items of work for the crew. One is queued by + * crew_start, and each worker may queue additional items. + */ +typedef struct work_tag { + struct work_tag *next; /* Next work item */ + char *path; /* Directory or file */ + char *string; /* Search string */ +} work_t, *work_p; + +/* + * One of these is initialized for each worker thread in the + * crew. It contains the "identity" of each worker. + */ +typedef struct worker_tag { + int index; /* Thread's index */ + pthread_t thread; /* Thread for stage */ + struct crew_tag *crew; /* Pointer to crew */ +} worker_t, *worker_p; + +/* + * The external "handle" for a work crew. Contains the + * crew synchronization state and staging area. + */ +typedef struct crew_tag { + int crew_size; /* Size of array */ + worker_t crew[CREW_SIZE];/* Crew members */ + long work_count; /* Count of work items */ + work_t *first, *last; /* First & last work item */ + pthread_mutex_t mutex; /* Mutex for crew data */ + pthread_cond_t done; /* Wait for crew done */ + pthread_cond_t go; /* Wait for work */ +} crew_t, *crew_p; + +size_t path_max; /* Filepath length */ +size_t name_max; /* Name length */ + +/* + * The thread start routine for crew threads. Waits until "go" + * command, processes work items until requested to shut down. + */ +void *worker_routine (void *arg) +{ + worker_p mine = (worker_t*)arg; + crew_p crew = mine->crew; + work_p work, new_work; + struct stat filestat; + struct dirent *entry; + int status; + + /* + * "struct dirent" is funny, because POSIX doesn't require + * the definition to be more than a header for a variable + * buffer. Thus, allocate a "big chunk" of memory, and use + * it as a buffer. + */ + entry = (struct dirent*)malloc ( + sizeof (struct dirent) + name_max); + if (entry == NULL) + errno_abort ("Allocating dirent"); + + status = pthread_mutex_lock (&crew->mutex); + if (status != 0) + err_abort (status, "Lock crew mutex"); + + /* + * There won't be any work when the crew is created, so wait + * until something's put on the queue. + */ + while (crew->work_count == 0) { + status = pthread_cond_wait (&crew->go, &crew->mutex); + if (status != 0) + err_abort (status, "Wait for go"); + } + + status = pthread_mutex_unlock (&crew->mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + + DPRINTF (("Crew %d starting\n", mine->index)); + + /* + * Now, as long as there's work, keep doing it. + */ + while (1) { + /* + * Wait while there is nothing to do, and + * the hope of something coming along later. If + * crew->first is NULL, there's no work. But if + * crew->work_count goes to zero, we're done. + */ + status = pthread_mutex_lock (&crew->mutex); + if (status != 0) + err_abort (status, "Lock crew mutex"); + + DPRINTF (("Crew %d top: first is %#lx, count is %d\n", + mine->index, crew->first, crew->work_count)); + while (crew->first == NULL) { + status = pthread_cond_wait (&crew->go, &crew->mutex); + if (status != 0) + err_abort (status, "Wait for work"); + } + + DPRINTF (("Crew %d woke: %#lx, %d\n", + mine->index, crew->first, crew->work_count)); + + /* + * Remove and process a work item + */ + work = crew->first; + crew->first = work->next; + if (crew->first == NULL) + crew->last = NULL; + + DPRINTF (("Crew %d took %#lx, leaves first %#lx, last %#lx\n", + mine->index, work, crew->first, crew->last)); + + status = pthread_mutex_unlock (&crew->mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + + /* + * We have a work item. Process it, which may involve + * queuing new work items. + */ + status = lstat (work->path, &filestat); + + if (S_ISLNK (filestat.st_mode)) + printf ( + "Thread %d: %s is a link, skipping.\n", + mine->index, + work->path); + else if (S_ISDIR (filestat.st_mode)) { + DIR *directory; + struct dirent *result; + + /* + * If the file is a directory, search it and place + * all files onto the queue as new work items. + */ + directory = opendir (work->path); + if (directory == NULL) { + fprintf ( + stderr, "Unable to open directory %s: %d (%s)\n", + work->path, + errno, strerror (errno)); + continue; + } + + while (1) { + status = readdir_r (directory, entry, &result); + if (status != 0) { + fprintf ( + stderr, + "Unable to read directory %s: %d (%s)\n", + work->path, + status, strerror (status)); + break; + } + if (result == NULL) + break; /* End of directory */ + + /* + * Ignore "." and ".." entries. + */ + if (strcmp (entry->d_name, ".") == 0) + continue; + if (strcmp (entry->d_name, "..") == 0) + continue; + new_work = (work_p)malloc (sizeof (work_t)); + if (new_work == NULL) + errno_abort ("Unable to allocate space"); + new_work->path = (char*)malloc (path_max); + if (new_work->path == NULL) + errno_abort ("Unable to allocate path"); + strcpy (new_work->path, work->path); + strcat (new_work->path, "/"); + strcat (new_work->path, entry->d_name); + new_work->string = work->string; + new_work->next = NULL; + status = pthread_mutex_lock (&crew->mutex); + if (status != 0) + err_abort (status, "Lock mutex"); + if (crew->first == NULL) { + crew->first = new_work; + crew->last = new_work; + } else { + crew->last->next = new_work; + crew->last = new_work; + } + crew->work_count++; + DPRINTF (( + "Crew %d: add work %#lx, first %#lx, last %#lx, %d\n", + mine->index, new_work, crew->first, + crew->last, crew->work_count)); + status = pthread_cond_signal (&crew->go); + status = pthread_mutex_unlock (&crew->mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + } + + closedir (directory); + } else if (S_ISREG (filestat.st_mode)) { + FILE *search; + char buffer[256], *bufptr, *search_ptr; + + /* + * If this is a file, not a directory, then search + * it for the string. + */ + search = fopen (work->path, "r"); + if (search == NULL) + fprintf ( + stderr, "Unable to open %s: %d (%s)\n", + work->path, + errno, strerror (errno)); + else { + + while (1) { + bufptr = fgets ( + buffer, sizeof (buffer), search); + if (bufptr == NULL) { + if (feof (search)) + break; + if (ferror (search)) { + fprintf ( + stderr, + "Unable to read %s: %d (%s)\n", + work->path, + errno, strerror (errno)); + break; + } + } + search_ptr = strstr (buffer, work->string); + if (search_ptr != NULL) { + flockfile (stdout); + printf ( + "Thread %d found \"%s\" in %s\n", + mine->index, work->string, work->path); +#if 0 + printf ("%s\n", buffer); +#endif + funlockfile (stdout); + break; + } + } + fclose (search); + } + } else + fprintf ( + stderr, + "Thread %d: %s is type %o (%s))\n", + mine->index, + work->path, + filestat.st_mode & S_IFMT, + (S_ISFIFO (filestat.st_mode) ? "FIFO" + : (S_ISCHR (filestat.st_mode) ? "CHR" + : (S_ISBLK (filestat.st_mode) ? "BLK" + : (S_ISSOCK (filestat.st_mode) ? "SOCK" + : "unknown"))))); + + free (work->path); /* Free path buffer */ + free (work); /* We're done with this */ + + /* + * Decrement count of outstanding work items, and wake + * waiters (trying to collect results or start a new + * calculation) if the crew is now idle. + * + * It's important that the count be decremented AFTER + * processing the current work item. That ensures the + * count won't go to 0 until we're really done. + */ + status = pthread_mutex_lock (&crew->mutex); + if (status != 0) + err_abort (status, "Lock crew mutex"); + + crew->work_count--; + DPRINTF (("Crew %d decremented work to %d\n", mine->index, + crew->work_count)); + if (crew->work_count <= 0) { + DPRINTF (("Crew thread %d done\n", mine->index)); + status = pthread_cond_broadcast (&crew->done); + if (status != 0) + err_abort (status, "Wake waiters"); + status = pthread_mutex_unlock (&crew->mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + break; + } + + status = pthread_mutex_unlock (&crew->mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + + } + + free (entry); + return NULL; +} + +/* + * Create a work crew. + */ +int crew_create (crew_t *crew, int crew_size) +{ + int crew_index; + int status; + + /* + * We won't create more than CREW_SIZE members + */ + if (crew_size > CREW_SIZE) + return EINVAL; + + crew->crew_size = crew_size; + crew->work_count = 0; + crew->first = NULL; + crew->last = NULL; + + /* + * Initialize synchronization objects + */ + status = pthread_mutex_init (&crew->mutex, NULL); + if (status != 0) + return status; + status = pthread_cond_init (&crew->done, NULL); + if (status != 0) + return status; + status = pthread_cond_init (&crew->go, NULL); + if (status != 0) + return status; + + /* + * Create the worker threads. + */ + for (crew_index = 0; crew_index < CREW_SIZE; crew_index++) { + crew->crew[crew_index].index = crew_index; + crew->crew[crew_index].crew = crew; + status = pthread_create (&crew->crew[crew_index].thread, + NULL, worker_routine, (void*)&crew->crew[crew_index]); + if (status != 0) + err_abort (status, "Create worker"); + } + return 0; +} + +/* + * Pass a file path to a work crew previously created + * using crew_create + */ +int crew_start ( + crew_p crew, + char *filepath, + char *search) +{ + work_p request; + int status; + + status = pthread_mutex_lock (&crew->mutex); + if (status != 0) + return status; + + /* + * If the crew is busy, wait for them to finish. + */ + while (crew->work_count > 0) { + status = pthread_cond_wait (&crew->done, &crew->mutex); + if (status != 0) { + pthread_mutex_unlock (&crew->mutex); + return status; + } + } + + errno = 0; + path_max = pathconf (filepath, _PC_PATH_MAX); + if (path_max == -1) { + if (errno == 0) + path_max = 1024; /* "No limit" */ + else + errno_abort ("Unable to get PATH_MAX"); + } + errno = 0; + name_max = pathconf (filepath, _PC_NAME_MAX); + if (name_max == -1) { + if (errno == 0) + name_max = 256; /* "No limit" */ + else + errno_abort ("Unable to get NAME_MAX"); + } + DPRINTF (( + "PATH_MAX for %s is %ld, NAME_MAX is %ld\n", + filepath, path_max, name_max)); + path_max++; /* Add null byte */ + name_max++; /* Add null byte */ + request = (work_p)malloc (sizeof (work_t)); + if (request == NULL) + errno_abort ("Unable to allocate request"); + DPRINTF (("Requesting %s\n", filepath)); + request->path = (char*)malloc (path_max); + if (request->path == NULL) + errno_abort ("Unable to allocate path"); + strcpy (request->path, filepath); + request->string = search; + request->next = NULL; + if (crew->first == NULL) { + crew->first = request; + crew->last = request; + } else { + crew->last->next = request; + crew->last = request; + } + + crew->work_count++; + status = pthread_cond_signal (&crew->go); + if (status != 0) { + free (crew->first); + crew->first = NULL; + crew->work_count = 0; + pthread_mutex_unlock (&crew->mutex); + return status; + } + while (crew->work_count > 0) { + status = pthread_cond_wait (&crew->done, &crew->mutex); + if (status != 0) + err_abort (status, "waiting for crew to finish"); + } + status = pthread_mutex_unlock (&crew->mutex); + if (status != 0) + err_abort (status, "Unlock crew mutex"); + return 0; +} + +/* + * The main program to "drive" the crew... + */ +int main (int argc, char *argv[]) +{ + crew_t my_crew; + char line[128], *next; + int status; + + if (argc < 3) { + fprintf (stderr, "Usage: %s string path\n", argv[0]); + return -1; + } + +#ifdef sun + /* + * On Solaris 2.5, threads are not timesliced. To ensure + * that our threads can run concurrently, we need to + * increase the concurrency level to CREW_SIZE. + */ + DPRINTF (("Setting concurrency level to %d\n", CREW_SIZE)); + thr_setconcurrency (CREW_SIZE); +#endif + status = crew_create (&my_crew, CREW_SIZE); + if (status != 0) + err_abort (status, "Create crew"); + + status = crew_start (&my_crew, argv[2], argv[1]); + if (status != 0) + err_abort (status, "Start crew"); + + return 0; +} diff --git a/errors.h b/errors.h new file mode 100644 index 0000000..c9c1c3a --- /dev/null +++ b/errors.h @@ -0,0 +1,49 @@ +#ifndef __errors_h +#define __errors_h + +#include +#include +#include +#include +#include + +/* + * Define a macro that can be used for diagnostic output from + * examples. When compiled -DDEBUG, it results in calling printf + * with the specified argument list. When DEBUG is not defined, it + * expands to nothing. + */ +#ifdef DEBUG +# define DPRINTF(arg) printf arg +#else +# define DPRINTF(arg) +#endif + +/* + * NOTE: the "do {" ... "} while (0);" bracketing around the macros + * allows the err_abort and errno_abort macros to be used as if they + * were function calls, even in contexts where a trailing ";" would + * generate a null statement. For example, + * + * if (status != 0) + * err_abort (status, "message"); + * else + * return status; + * + * will not compile if err_abort is a macro ending with "}", because + * C does not expect a ";" to follow the "}". Because C does expect + * a ";" following the ")" in the do...while construct, err_abort and + * errno_abort can be used as if they were function calls. + */ +#define err_abort(code,text) do { \ + fprintf (stderr, "%s at \"%s\":%d: %s\n", \ + text, __FILE__, __LINE__, strerror (code)); \ + abort (); \ + } while (0) +#define errno_abort(text) do { \ + fprintf (stderr, "%s at \"%s\":%d: %s\n", \ + text, __FILE__, __LINE__, strerror (errno)); \ + abort (); \ + } while (0) + +#endif diff --git a/flock.c b/flock.c new file mode 100644 index 0000000..3b459f7 --- /dev/null +++ b/flock.c @@ -0,0 +1,84 @@ +/* + * flock.c + * + * Demonstrate use of stdio file locking to generate an "atomic" + * prompting sequence. The write to stdout and the read from + * stdin cannot be separated. + */ +#include +#include "errors.h" + +/* + * This routine writes a prompt to stdout (passed as the thread's + * "arg"), and reads a response. All other I/O to stdin and stdout + * is prevented by the file locks until both prompt and fgets are + * complete. + */ +void *prompt_routine (void *arg) +{ + char *prompt = (char*)arg; + char *string; + int len; + + string = (char*)malloc (128); + if (string == NULL) + errno_abort ("Alloc string"); + flockfile (stdin); + flockfile (stdout); + printf (prompt); + if (fgets (string, 128, stdin) == NULL) + string[0] = '\0'; + else { + len = strlen (string); + if (len > 0 && string[len-1] == '\n') + string[len-1] = '\0'; + } + funlockfile (stdout); + funlockfile (stdin); + return (void*)string; +} + +int main (int argc, char *argv[]) +{ + pthread_t thread1, thread2, thread3; + char *string; + int status; + +#ifdef sun + /* + * On Solaris 2.5, threads are not timesliced. To ensure + * that our threads can run concurrently, we need to + * increase the concurrency level. + */ + DPRINTF (("Setting concurrency level to 4\n")); + thr_setconcurrency (4); +#endif + status = pthread_create ( + &thread1, NULL, prompt_routine, "Thread 1> "); + if (status != 0) + err_abort (status, "Create thread"); + status = pthread_create ( + &thread2, NULL, prompt_routine, "Thread 2> "); + if (status != 0) + err_abort (status, "Create thread"); + status = pthread_create ( + &thread3, NULL, prompt_routine, "Thread 3> "); + if (status != 0) + err_abort (status, "Create thread"); + status = pthread_join (thread1, (void**)&string); + if (status != 0) + err_abort (status, "Join thread"); + printf ("Thread 1: \"%s\"\n", string); + free (string); + status = pthread_join (thread2, (void**)&string); + if (status != 0) + err_abort (status, "Join thread"); + printf ("Thread 1: \"%s\"\n", string); + free (string); + status = pthread_join (thread3, (void**)&string); + if (status != 0) + err_abort (status, "Join thread"); + printf ("Thread 1: \"%s\"\n", string); + free (string); + return 0; +} diff --git a/getlogin.c b/getlogin.c new file mode 100644 index 0000000..829ef66 --- /dev/null +++ b/getlogin.c @@ -0,0 +1,42 @@ +/* + * getlogin.c + * + * Print information on login name and terminal, to demonstrate + * the thread-safe functions. + */ +#include +#include "errors.h" + +/* + * If either TTY_NAME_MAX or LOGIN_NAME_MAX are undefined + * (this means they are "indeterminate" values), assume a + * reasonable size (for simplicity) rather than using sysconf + * and dynamically allocating the buffers. + */ +#ifndef TTY_NAME_MAX +# define TTY_NAME_MAX 128 +#endif +#ifndef LOGIN_NAME_MAX +# define LOGIN_NAME_MAX 32 +#endif + +int main (int argc, char *argv[]) +{ + char login_str[LOGIN_NAME_MAX]; + char stdin_str[TTY_NAME_MAX]; + char cterm_str[L_ctermid], *cterm_str_ptr; + int status; + + status = getlogin_r (login_str, sizeof (login_str)); + if (status != 0) + err_abort (status, "Get login"); + cterm_str_ptr = ctermid (cterm_str); + if (cterm_str_ptr == NULL) + errno_abort ("Get cterm"); + status = ttyname_r (0, stdin_str, sizeof (stdin_str)); + if (status != 0) + err_abort (status, "Get stdin"); + printf ("User: %s, cterm: %s, fd 0: %s\n", + login_str, cterm_str, stdin_str); + return 0; +} diff --git a/hello.c b/hello.c new file mode 100644 index 0000000..aa64515 --- /dev/null +++ b/hello.c @@ -0,0 +1,28 @@ +/* + * hello.c + * + * Create a very simple thread that says "Hello world", just + * because it has to be here. + */ +#include +#include "errors.h" + +void *hello_world (void *arg) +{ + printf ("Hello world\n"); + return NULL; +} + +int main (int argc, char *argv[]) +{ + pthread_t hello_id; + int status; + + status = pthread_create (&hello_id, NULL, hello_world, NULL); + if (status != 0) + err_abort (status, "Create thread"); + status = pthread_join (hello_id, NULL); + if (status != 0) + err_abort (status, "Join thread"); + return 0; +} diff --git a/inertia.c b/inertia.c new file mode 100644 index 0000000..936cbcf --- /dev/null +++ b/inertia.c @@ -0,0 +1,56 @@ +/* + * inertia.c + * + * Demonstrate a class of programming error, assuming that + * threads can't start right away. + * + * Special notes: On a Solaris 2.5 uniprocessor, this test can't + * fail unless a second LWP is created by calling + * thr_setconcurrency() because threads are not timesliced. + */ +#include +#include "errors.h" + +void *printer_thread (void *arg) +{ + char *string = *(char**)arg; + + printf ("%s\n", string); + return NULL; +} + +int main (int argc, char *argv[]) +{ + pthread_t printer_id; + char *string_ptr; + int i, status; + +#ifdef sun + /* + * On Solaris 2.5, threads are not timesliced. To ensure + * that our two threads can run concurrently, we need to + * increase the concurrency level to 2. + */ + DPRINTF (("Setting concurrency level to 2\n")); + thr_setconcurrency (2); +#endif + string_ptr = "Before value"; + status = pthread_create ( + &printer_id, NULL, printer_thread, (void*)&string_ptr); + if (status != 0) + err_abort (status, "Create thread"); + + /* + * Give the thread a chance to get started if it's going to run + * in parallel, but not enough that the current thread is likely + * to be timesliced. (This is a tricky balance, and the loop may + * need to be adjusted on your system before you can see the bug.) + */ + for (i = 0; i < 10000000; i++); + + string_ptr = "After value"; + status = pthread_join (printer_id, NULL); + if (status != 0) + err_abort (status, "Join thread"); + return 0; +} diff --git a/lifecycle.c b/lifecycle.c new file mode 100644 index 0000000..8b99120 --- /dev/null +++ b/lifecycle.c @@ -0,0 +1,36 @@ +/* + * lifecycle.c + * + * Demonstrate the "life cycle" of a typical thread. A thread is + * created, and then joined. + */ +#include +#include "errors.h" + +/* + * Thread start routine. + */ +void *thread_routine (void *arg) +{ + return arg; +} + +main (int argc, char *argv[]) +{ + pthread_t thread_id; + void *thread_result; + int status; + + status = pthread_create ( + &thread_id, NULL, thread_routine, NULL); + if (status != 0) + err_abort (status, "Create thread"); + + status = pthread_join (thread_id, &thread_result); + if (status != 0) + err_abort (status, "Join thread"); + if (thread_result == NULL) + return 0; + else + return 1; +} diff --git a/mutex_attr.c b/mutex_attr.c new file mode 100644 index 0000000..5b038a8 --- /dev/null +++ b/mutex_attr.c @@ -0,0 +1,35 @@ +/* + * mutex_attr.c + * + * Create a mutex using a non-default attributes object, + * mutex_attr. If the implementation supports the pshared + * attribute, the mutex is created "process private" so that it + * could be used to synchronize between threads in separate + * address spaces. (Note that, to create a "process shared" + * mutex, the pthread_mutex_t itself must be placed in shared + * memory that is accessible to all threads using the mutex.) + */ +#include +#include "errors.h" + +pthread_mutex_t mutex; + +int main (int argc, char *argv[]) +{ + pthread_mutexattr_t mutex_attr; + int status; + + status = pthread_mutexattr_init (&mutex_attr); + if (status != 0) + err_abort (status, "Create attr"); +#ifdef _POSIX_THREAD_PROCESS_SHARED + status = pthread_mutexattr_setpshared ( + &mutex_attr, PTHREAD_PROCESS_PRIVATE); + if (status != 0) + err_abort (status, "Set pshared"); +#endif + status = pthread_mutex_init (&mutex, &mutex_attr); + if (status != 0) + err_abort (status, "Init mutex"); + return 0; +} diff --git a/mutex_dynamic.c b/mutex_dynamic.c new file mode 100644 index 0000000..5289502 --- /dev/null +++ b/mutex_dynamic.c @@ -0,0 +1,33 @@ +/* + * mutex_dynamic.c + * + * Demonstrate dynamic initialization of a mutex. + */ +#include +#include "errors.h" + +/* + * Define a structure, with a mutex. + */ +typedef struct my_struct_tag { + pthread_mutex_t mutex; /* Protects access to value */ + int value; /* Access protected by mutex */ +} my_struct_t; + +int main (int argc, char *argv[]) +{ + my_struct_t *data; + int status; + + data = malloc (sizeof (my_struct_t)); + if (data == NULL) + errno_abort ("Allocate structure"); + status = pthread_mutex_init (&data->mutex, NULL); + if (status != 0) + err_abort (status, "Init mutex"); + status = pthread_mutex_destroy (&data->mutex); + if (status != 0) + err_abort (status, "Destroy mutex"); + (void)free (data); + return status; +} diff --git a/mutex_static.c b/mutex_static.c new file mode 100644 index 0000000..41f5685 --- /dev/null +++ b/mutex_static.c @@ -0,0 +1,23 @@ +/* + * mutex_static.c + * + * Demonstrate static initialization of a mutex. + */ +#include +#include "errors.h" + +/* + * Declare a structure, with a mutex, statically initialized. This is the + * same as using pthread_mutex_init, with the default attributes. + */ +typedef struct my_struct_tag { + pthread_mutex_t mutex; /* Protects access to value */ + int value; /* Access protected by mutex */ +} my_struct_t; + +my_struct_t data = {PTHREAD_MUTEX_INITIALIZER, 0}; + +int main (int argc, char *argv[]) +{ + return 0; +} diff --git a/once.c b/once.c new file mode 100644 index 0000000..a6e104e --- /dev/null +++ b/once.c @@ -0,0 +1,71 @@ +/* + * once.c + * + * Demonstrate the use of pthread_once() one-time + * initialization. + */ +#include +#include "errors.h" + +pthread_once_t once_block = PTHREAD_ONCE_INIT; +pthread_mutex_t mutex; + +/* + * This is the one-time initialization routine. It will be + * called exactly once, no matter how many calls to pthread_once + * with the same control structure are made during the course of + * the program. + */ +void once_init_routine (void) +{ + int status; + + status = pthread_mutex_init (&mutex, NULL); + if (status != 0) + err_abort (status, "Init Mutex"); +} + +/* + * Thread start routine that calls pthread_once. + */ +void *thread_routine (void *arg) +{ + int status; + + status = pthread_once (&once_block, once_init_routine); + if (status != 0) + err_abort (status, "Once init"); + status = pthread_mutex_lock (&mutex); + if (status != 0) + err_abort (status, "Lock mutex"); + printf ("thread_routine has locked the mutex.\n"); + status = pthread_mutex_unlock (&mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + return NULL; +} + +int main (int argc, char *argv[]) +{ + pthread_t thread_id; + char *input, buffer[64]; + int status; + + status = pthread_create (&thread_id, NULL, thread_routine, NULL); + if (status != 0) + err_abort (status, "Create thread"); + status = pthread_once (&once_block, once_init_routine); + if (status != 0) + err_abort (status, "Once init"); + status = pthread_mutex_lock (&mutex); + if (status != 0) + err_abort (status, "Lock mutex"); + printf ("Main has locked the mutex.\n"); + status = pthread_mutex_unlock (&mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + status = pthread_join (thread_id, NULL); + if (status != 0) + err_abort (status, "Join thread"); + return 0; +} diff --git a/pipe.c b/pipe.c new file mode 100644 index 0000000..cf3a23e --- /dev/null +++ b/pipe.c @@ -0,0 +1,263 @@ +/* + * pipe.c + * + * Simple demonstration of a pipeline. main() is a loop that + * feeds the pipeline with integer values. Each stage of the + * pipeline increases the integer by one before passing it along + * to the next. Entering the command "=" reads the pipeline + * result. (Notice that too many '=' commands will hang.) + */ +#include +#include "errors.h" + +/* + * Internal structure describing a "stage" in the + * pipeline. One for each thread, plus a "result + * stage" where the final thread can stash the value. + */ +typedef struct stage_tag { + pthread_mutex_t mutex; /* Protect data */ + pthread_cond_t avail; /* Data available */ + pthread_cond_t ready; /* Ready for data */ + int data_ready; /* Data present */ + long data; /* Data to process */ + pthread_t thread; /* Thread for stage */ + struct stage_tag *next; /* Next stage */ +} stage_t; + +/* + * External structure representing the entire + * pipeline. + */ +typedef struct pipe_tag { + pthread_mutex_t mutex; /* Mutex to protect pipe */ + stage_t *head; /* First stage */ + stage_t *tail; /* Final stage */ + int stages; /* Number of stages */ + int active; /* Active data elements */ +} pipe_t; + +/* + * Internal function to send a "message" to the + * specified pipe stage. Threads use this to pass + * along the modified data item. + */ +int pipe_send (stage_t *stage, long data) +{ + int status; + + status = pthread_mutex_lock (&stage->mutex); + if (status != 0) + return status; + /* + * If there's data in the pipe stage, wait for it + * to be consumed. + */ + while (stage->data_ready) { + status = pthread_cond_wait (&stage->ready, &stage->mutex); + if (status != 0) { + pthread_mutex_unlock (&stage->mutex); + return status; + } + } + + /* + * Send the new data + */ + stage->data = data; + stage->data_ready = 1; + status = pthread_cond_signal (&stage->avail); + if (status != 0) { + pthread_mutex_unlock (&stage->mutex); + return status; + } + status = pthread_mutex_unlock (&stage->mutex); + return status; +} + +/* + * The thread start routine for pipe stage threads. + * Each will wait for a data item passed from the + * caller or the previous stage, modify the data + * and pass it along to the next (or final) stage. + */ +void *pipe_stage (void *arg) +{ + stage_t *stage = (stage_t*)arg; + stage_t *next_stage = stage->next; + int status; + + status = pthread_mutex_lock (&stage->mutex); + if (status != 0) + err_abort (status, "Lock pipe stage"); + while (1) { + while (stage->data_ready != 1) { + status = pthread_cond_wait (&stage->avail, &stage->mutex); + if (status != 0) + err_abort (status, "Wait for previous stage"); + } + pipe_send (next_stage, stage->data + 1); + stage->data_ready = 0; + status = pthread_cond_signal (&stage->ready); + if (status != 0) + err_abort (status, "Wake next stage"); + } + /* + * Notice that the routine never unlocks the stage->mutex. + * The call to pthread_cond_wait implicitly unlocks the + * mutex while the thread is waiting, allowing other threads + * to make progress. Because the loop never terminates, this + * function has no need to unlock the mutex explicitly. + */ +} + +/* + * External interface to create a pipeline. All the + * data is initialized and the threads created. They'll + * wait for data. + */ +int pipe_create (pipe_t *pipe, int stages) +{ + int pipe_index; + stage_t **link = &pipe->head, *new_stage, *stage; + int status; + + status = pthread_mutex_init (&pipe->mutex, NULL); + if (status != 0) + err_abort (status, "Init pipe mutex"); + pipe->stages = stages; + pipe->active = 0; + + for (pipe_index = 0; pipe_index <= stages; pipe_index++) { + new_stage = (stage_t*)malloc (sizeof (stage_t)); + if (new_stage == NULL) + errno_abort ("Allocate stage"); + status = pthread_mutex_init (&new_stage->mutex, NULL); + if (status != 0) + err_abort (status, "Init stage mutex"); + status = pthread_cond_init (&new_stage->avail, NULL); + if (status != 0) + err_abort (status, "Init avail condition"); + status = pthread_cond_init (&new_stage->ready, NULL); + if (status != 0) + err_abort (status, "Init ready condition"); + new_stage->data_ready = 0; + *link = new_stage; + link = &new_stage->next; + } + + *link = (stage_t*)NULL; /* Terminate list */ + pipe->tail = new_stage; /* Record the tail */ + + /* + * Create the threads for the pipe stages only after all + * the data is initialized (including all links). Note + * that the last stage doesn't get a thread, it's just + * a receptacle for the final pipeline value. + * + * At this point, proper cleanup on an error would take up + * more space than worthwhile in a "simple example", so + * instead of cancelling and detaching all the threads + * already created, plus the synchronization object and + * memory cleanup done for earlier errors, it will simply + * abort. + */ + for ( stage = pipe->head; + stage->next != NULL; + stage = stage->next) { + status = pthread_create ( + &stage->thread, NULL, pipe_stage, (void*)stage); + if (status != 0) + err_abort (status, "Create pipe stage"); + } + return 0; +} + +/* + * External interface to start a pipeline by passing + * data to the first stage. The routine returns while + * the pipeline processes in parallel. Call the + * pipe_result return to collect the final stage values + * (note that the pipe will stall when each stage fills, + * until the result is collected). + */ +int pipe_start (pipe_t *pipe, long value) +{ + int status; + + status = pthread_mutex_lock (&pipe->mutex); + if (status != 0) + err_abort (status, "Lock pipe mutex"); + pipe->active++; + status = pthread_mutex_unlock (&pipe->mutex); + if (status != 0) + err_abort (status, "Unlock pipe mutex"); + pipe_send (pipe->head, value); + return 0; +} + +/* + * Collect the result of the pipeline. Wait for a + * result if the pipeline hasn't produced one. + */ +int pipe_result (pipe_t *pipe, long *result) +{ + stage_t *tail = pipe->tail; + long value; + int empty = 0; + int status; + + status = pthread_mutex_lock (&pipe->mutex); + if (status != 0) + err_abort (status, "Lock pipe mutex"); + if (pipe->active <= 0) + empty = 1; + else + pipe->active--; + + status = pthread_mutex_unlock (&pipe->mutex); + if (status != 0) + err_abort (status, "Unlock pipe mutex"); + if (empty) + return 0; + + pthread_mutex_lock (&tail->mutex); + while (!tail->data_ready) + pthread_cond_wait (&tail->avail, &tail->mutex); + *result = tail->data; + tail->data_ready = 0; + pthread_cond_signal (&tail->ready); + pthread_mutex_unlock (&tail->mutex); + return 1; +} + +/* + * The main program to "drive" the pipeline... + */ +int main (int argc, char *argv[]) +{ + pipe_t my_pipe; + long value, result; + int status; + char line[128]; + + pipe_create (&my_pipe, 10); + printf ("Enter integer values, or \"=\" for next result\n"); + + while (1) { + printf ("Data> "); + if (fgets (line, sizeof (line), stdin) == NULL) exit (0); + if (strlen (line) <= 1) continue; + if (strlen (line) <= 2 && line[0] == '=') { + if (pipe_result (&my_pipe, &result)) + printf ("Result is %ld\n", result); + else + printf ("Pipe is empty\n"); + } else { + if (sscanf (line, "%ld", &value) < 1) + fprintf (stderr, "Enter an integer value\n"); + else + pipe_start (&my_pipe, value); + } + } +} diff --git a/putchar.c b/putchar.c new file mode 100644 index 0000000..b871498 --- /dev/null +++ b/putchar.c @@ -0,0 +1,96 @@ +/* + * putchar.c + * + * Demonstrate use of stdio file locking to generate an "atomic" + * sequence of character writes (using putchar). If run with an + * argument of "1", or no argument, the program uses a sequence + * of putchar_unlocked calls within flockfile/funlockfile to + * ensure that one threads writes cannot be interleaved with + * another's. + * + * With an argument of "0", the program uses putchar, without + * file locks, to show that the writes may be interleaved. + * + * The putchar[_unlocked] loop is punctuated with sleep(1) calls + * to ensure that the desired behavior is demonstrated. Without + * some delay, even on a multiprocessor the program may often + * fail to display the interleaved output in this simplified + * case. + * + * With file locking, you can expect to see the following output: + * + * thread 1 + * thread 2 + * thread 3 + * + * While without file locking, you can expect to see something + * much less predictable, but probably resembling this: + * + * ttthhhiiisss iiisss ttthhhrrreeeaaaddd 123 + * + */ +#include +#include "errors.h" + +/* + * This function writes a string (the function's arg) to stdout, + * by locking the file stream and using putchar_unlocked to + * write each character individually. + */ +void *lock_routine (void *arg) +{ + char *pointer; + + flockfile (stdout); + for (pointer = arg; *pointer != '\0'; pointer++) { + putchar_unlocked (*pointer); + sleep (1); + } + funlockfile (stdout); + return NULL; +} + +/* + * This function writes a string (the function's arg) to stdout, + * by using putchar to write each character individually. + * Although the internal locking of putchar prevents file stream + * corruption, the writes of various threads may be interleaved. + */ +void *unlock_routine (void *arg) +{ + char *pointer; + + for (pointer = arg; *pointer != '\0'; pointer++) { + putchar (*pointer); + sleep (1); + } + return NULL; +} + +int main (int argc, char *argv[]) +{ + pthread_t thread1, thread2, thread3; + int flock_flag = 1; + void *(*thread_func)(void *); + int status; + + if (argc > 1) + flock_flag = atoi (argv[1]); + if (flock_flag) + thread_func = lock_routine; + else + thread_func = unlock_routine; + status = pthread_create ( + &thread1, NULL, thread_func, "this is thread 1\n"); + if (status != 0) + err_abort (status, "Create thread"); + status = pthread_create ( + &thread2, NULL, thread_func, "this is thread 2\n"); + if (status != 0) + err_abort (status, "Create thread"); + status = pthread_create ( + &thread3, NULL, thread_func, "this is thread 3\n"); + if (status != 0) + err_abort (status, "Create thread"); + pthread_exit (NULL); +} diff --git a/rwlock.c b/rwlock.c new file mode 100644 index 0000000..bbcc2a8 --- /dev/null +++ b/rwlock.c @@ -0,0 +1,276 @@ +/* + * rwlock.c + * + * This file implements the "read-write lock" synchronization + * construct. + * + * A read-write lock allows a thread to lock shared data either + * for shared read access or exclusive write access. + * + * The rwl_init() and rwl_destroy() functions, respectively, + * allow you to initialize/create and destroy/free the + * read-write lock. + * + * The rwl_readlock() function locks a read-write lock for + * shared read access, and rwl_readunlock() releases the + * lock. rwl_readtrylock() attempts to lock a read-write lock + * for read access, and returns EBUSY instead of blocking. + * + * The rwl_writelock() function locks a read-write lock for + * exclusive write access, and rwl_writeunlock() releases the + * lock. rwl_writetrylock() attempts to lock a read-write lock + * for write access, and returns EBUSY instead of blocking. + */ +#include +#include "errors.h" +#include "rwlock.h" + +/* + * Initialize a read-write lock + */ +int rwl_init (rwlock_t *rwl) +{ + int status; + + rwl->r_active = 0; + rwl->r_wait = rwl->w_wait = 0; + rwl->w_active = 0; + status = pthread_mutex_init (&rwl->mutex, NULL); + if (status != 0) + return status; + status = pthread_cond_init (&rwl->read, NULL); + if (status != 0) { + /* if unable to create read CV, destroy mutex */ + pthread_mutex_destroy (&rwl->mutex); + return status; + } + status = pthread_cond_init (&rwl->write, NULL); + if (status != 0) { + /* if unable to create write CV, destroy read CV and mutex */ + pthread_cond_destroy (&rwl->read); + pthread_mutex_destroy (&rwl->mutex); + return status; + } + rwl->valid = RWLOCK_VALID; + return 0; +} + +/* + * Destroy a read-write lock + */ +int rwl_destroy (rwlock_t *rwl) +{ + int status, status1, status2; + + if (rwl->valid != RWLOCK_VALID) + return EINVAL; + status = pthread_mutex_lock (&rwl->mutex); + if (status != 0) + return status; + + /* + * Check whether any threads own the lock; report "BUSY" if + * so. + */ + if (rwl->r_active > 0 || rwl->w_active) { + pthread_mutex_unlock (&rwl->mutex); + return EBUSY; + } + + /* + * Check whether any threads are known to be waiting; report + * EBUSY if so. + */ + if (rwl->r_wait != 0 || rwl->w_wait != 0) { + pthread_mutex_unlock (&rwl->mutex); + return EBUSY; + } + + rwl->valid = 0; + status = pthread_mutex_unlock (&rwl->mutex); + if (status != 0) + return status; + status = pthread_mutex_destroy (&rwl->mutex); + status1 = pthread_cond_destroy (&rwl->read); + status2 = pthread_cond_destroy (&rwl->write); + return (status == 0 ? status : (status1 == 0 ? status1 : status2)); +} + +/* + * Handle cleanup when the read lock condition variable + * wait is cancelled. + * + * Simply record that the thread is no longer waiting, + * and unlock the mutex. + */ +static void rwl_readcleanup (void *arg) +{ + rwlock_t *rwl = (rwlock_t *)arg; + + rwl->r_wait--; + pthread_mutex_unlock (&rwl->mutex); +} + +/* + * Lock a read-write lock for read access. + */ +int rwl_readlock (rwlock_t *rwl) +{ + int status; + + if (rwl->valid != RWLOCK_VALID) + return EINVAL; + status = pthread_mutex_lock (&rwl->mutex); + if (status != 0) + return status; + if (rwl->w_active) { + rwl->r_wait++; + pthread_cleanup_push (rwl_readcleanup, (void*)rwl); + while (rwl->w_active) { + status = pthread_cond_wait (&rwl->read, &rwl->mutex); + if (status != 0) + break; + } + pthread_cleanup_pop (0); + rwl->r_wait--; + } + if (status == 0) + rwl->r_active++; + pthread_mutex_unlock (&rwl->mutex); + return status; +} + +/* + * Attempt to lock a read-write lock for read access (don't + * block if unavailable). + */ +int rwl_readtrylock (rwlock_t *rwl) +{ + int status, status2; + + if (rwl->valid != RWLOCK_VALID) + return EINVAL; + status = pthread_mutex_lock (&rwl->mutex); + if (status != 0) + return status; + if (rwl->w_active) + status = EBUSY; + else + rwl->r_active++; + status2 = pthread_mutex_unlock (&rwl->mutex); + return (status2 != 0 ? status2 : status); +} + +/* + * Unlock a read-write lock from read access. + */ +int rwl_readunlock (rwlock_t *rwl) +{ + int status, status2; + + if (rwl->valid != RWLOCK_VALID) + return EINVAL; + status = pthread_mutex_lock (&rwl->mutex); + if (status != 0) + return status; + rwl->r_active--; + if (rwl->r_active == 0 && rwl->w_wait > 0) + status = pthread_cond_signal (&rwl->write); + status2 = pthread_mutex_unlock (&rwl->mutex); + return (status2 == 0 ? status : status2); +} + +/* + * Handle cleanup when the write lock condition variable + * wait is cancelled. + * + * Simply record that the thread is no longer waiting, + * and unlock the mutex. + */ +static void rwl_writecleanup (void *arg) +{ + rwlock_t *rwl = (rwlock_t *)arg; + + rwl->w_wait--; + pthread_mutex_unlock (&rwl->mutex); +} + +/* + * Lock a read-write lock for write access. + */ +int rwl_writelock (rwlock_t *rwl) +{ + int status; + + if (rwl->valid != RWLOCK_VALID) + return EINVAL; + status = pthread_mutex_lock (&rwl->mutex); + if (status != 0) + return status; + if (rwl->w_active || rwl->r_active > 0) { + rwl->w_wait++; + pthread_cleanup_push (rwl_writecleanup, (void*)rwl); + while (rwl->w_active || rwl->r_active > 0) { + status = pthread_cond_wait (&rwl->write, &rwl->mutex); + if (status != 0) + break; + } + pthread_cleanup_pop (0); + rwl->w_wait--; + } + if (status == 0) + rwl->w_active = 1; + pthread_mutex_unlock (&rwl->mutex); + return status; +} + +/* + * Attempt to lock a read-write lock for write access. Don't + * block if unavailable. + */ +int rwl_writetrylock (rwlock_t *rwl) +{ + int status, status2; + + if (rwl->valid != RWLOCK_VALID) + return EINVAL; + status = pthread_mutex_lock (&rwl->mutex); + if (status != 0) + return status; + if (rwl->w_active || rwl->r_active > 0) + status = EBUSY; + else + rwl->w_active = 1; + status2 = pthread_mutex_unlock (&rwl->mutex); + return (status != 0 ? status : status2); +} + +/* + * Unlock a read-write lock from write access. + */ +int rwl_writeunlock (rwlock_t *rwl) +{ + int status; + + if (rwl->valid != RWLOCK_VALID) + return EINVAL; + status = pthread_mutex_lock (&rwl->mutex); + if (status != 0) + return status; + rwl->w_active = 0; + if (rwl->r_wait > 0) { + status = pthread_cond_broadcast (&rwl->read); + if (status != 0) { + pthread_mutex_unlock (&rwl->mutex); + return status; + } + } else if (rwl->w_wait > 0) { + status = pthread_cond_signal (&rwl->write); + if (status != 0) { + pthread_mutex_unlock (&rwl->mutex); + return status; + } + } + status = pthread_mutex_unlock (&rwl->mutex); + return status; +} diff --git a/rwlock.h b/rwlock.h new file mode 100644 index 0000000..5ab1801 --- /dev/null +++ b/rwlock.h @@ -0,0 +1,49 @@ +/* + * rwlock.h + * + * This header file describes the "reader/writer lock" synchronization + * construct. The type rwlock_t describes the full state of the lock + * including the POSIX 1003.1c synchronization objects necessary. + * + * A reader/writer lock allows a thread to lock shared data either for shared + * read access or exclusive write access. + * + * The rwl_init() and rwl_destroy() functions, respectively, allow you to + * initialize/create and destroy/free the reader/writer lock. + */ +#include + +/* + * Structure describing a read-write lock. + */ +typedef struct rwlock_tag { + pthread_mutex_t mutex; + pthread_cond_t read; /* wait for read */ + pthread_cond_t write; /* wait for write */ + int valid; /* set when valid */ + int r_active; /* readers active */ + int w_active; /* writer active */ + int r_wait; /* readers waiting */ + int w_wait; /* writers waiting */ +} rwlock_t; + +#define RWLOCK_VALID 0xfacade + +/* + * Support static initialization of barriers + */ +#define RWL_INITIALIZER \ + {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, \ + PTHREAD_COND_INITIALIZER, RWLOCK_VALID, 0, 0, 0, 0} + +/* + * Define read-write lock functions + */ +extern int rwl_init (rwlock_t *rwlock); +extern int rwl_destroy (rwlock_t *rwlock); +extern int rwl_readlock (rwlock_t *rwlock); +extern int rwl_readtrylock (rwlock_t *rwlock); +extern int rwl_readunlock (rwlock_t *rwlock); +extern int rwl_writelock (rwlock_t *rwlock); +extern int rwl_writetrylock (rwlock_t *rwlock); +extern int rwl_writeunlock (rwlock_t *rwlock); diff --git a/rwlock_main.c b/rwlock_main.c new file mode 100644 index 0000000..3ed66b9 --- /dev/null +++ b/rwlock_main.c @@ -0,0 +1,167 @@ +/* + * rwlock_main.c + * + * Demonstrate use of read-write locks as implemented by + * rwlock.c + * + * Special notes: On a Solaris system, call thr_setconcurrency() + * to allow interleaved thread execution, since threads are not + * timesliced. + */ +#include "rwlock.h" +#include "errors.h" + +#define THREADS 5 +#define DATASIZE 15 +#define ITERATIONS 10000 + +/* + * Keep statistics for each thread. + */ +typedef struct thread_tag { + int thread_num; + pthread_t thread_id; + int updates; + int reads; + int interval; +} thread_t; + +/* + * Read-write lock and shared data + */ +typedef struct data_tag { + rwlock_t lock; + int data; + int updates; +} data_t; + +thread_t threads[THREADS]; +data_t data[DATASIZE]; + +/* + * Thread start routine that uses read-write locks + */ +void *thread_routine (void *arg) +{ + thread_t *self = (thread_t*)arg; + int repeats = 0; + int iteration; + int element = 0; + int status; + + for (iteration = 0; iteration < ITERATIONS; iteration++) { + /* + * Each "self->interval" iterations, perform an + * update operation (write lock instead of read + * lock). + */ + if ((iteration % self->interval) == 0) { + status = rwl_writelock (&data[element].lock); + if (status != 0) + err_abort (status, "Write lock"); + data[element].data = self->thread_num; + data[element].updates++; + self->updates++; + status = rwl_writeunlock (&data[element].lock); + if (status != 0) + err_abort (status, "Write unlock"); + } else { + /* + * Look at the current data element to see whether + * the current thread last updated it. Count the + * times, to report later. + */ + status = rwl_readlock (&data[element].lock); + if (status != 0) + err_abort (status, "Read lock"); + self->reads++; + if (data[element].data == self->thread_num) + repeats++; + status = rwl_readunlock (&data[element].lock); + if (status != 0) + err_abort (status, "Read unlock"); + } + element++; + if (element >= DATASIZE) + element = 0; + } + + if (repeats > 0) + printf ( + "Thread %d found unchanged elements %d times\n", + self->thread_num, repeats); + return NULL; +} + +int main (int argc, char *argv[]) +{ + int count; + int data_count; + int status; + unsigned int seed = 1; + int thread_updates = 0; + int data_updates = 0; + +#ifdef sun + /* + * On Solaris 2.5, threads are not timesliced. To ensure + * that our threads can run concurrently, we need to + * increase the concurrency level to THREADS. + */ + DPRINTF (("Setting concurrency level to %d\n", THREADS)); + thr_setconcurrency (THREADS); +#endif + + /* + * Initialize the shared data. + */ + for (data_count = 0; data_count < DATASIZE; data_count++) { + data[data_count].data = 0; + data[data_count].updates = 0; + status = rwl_init (&data[data_count].lock); + if (status != 0) + err_abort (status, "Init rw lock"); + } + + /* + * Create THREADS threads to access shared data. + */ + for (count = 0; count < THREADS; count++) { + threads[count].thread_num = count; + threads[count].updates = 0; + threads[count].reads = 0; + threads[count].interval = rand_r (&seed) % 71; + status = pthread_create (&threads[count].thread_id, + NULL, thread_routine, (void*)&threads[count]); + if (status != 0) + err_abort (status, "Create thread"); + } + + /* + * Wait for all threads to complete, and collect + * statistics. + */ + for (count = 0; count < THREADS; count++) { + status = pthread_join (threads[count].thread_id, NULL); + if (status != 0) + err_abort (status, "Join thread"); + thread_updates += threads[count].updates; + printf ("%02d: interval %d, updates %d, reads %d\n", + count, threads[count].interval, + threads[count].updates, threads[count].reads); + } + + /* + * Collect statistics for the data. + */ + for (data_count = 0; data_count < DATASIZE; data_count++) { + data_updates += data[data_count].updates; + printf ("data %02d: value %d, %d updates\n", + data_count, data[data_count].data, data[data_count].updates); + rwl_destroy (&data[data_count].lock); + } + + printf ("%d thread updates, %d data updates\n", + thread_updates, data_updates); + return 0; +} diff --git a/rwlock_try_main.c b/rwlock_try_main.c new file mode 100644 index 0000000..c6d67ac --- /dev/null +++ b/rwlock_try_main.c @@ -0,0 +1,156 @@ +/* + * rwlock_try_main.c + * + * Demonstrate use of non-blocking read-write locks. + * + * Special notes: On a Solaris system, call thr_setconcurrency() + * to allow interleaved thread execution, since threads are not + * timesliced. + */ +#include +#include "rwlock.h" +#include "errors.h" + +#define THREADS 5 +#define ITERATIONS 1000 +#define DATASIZE 15 + +/* + * Keep statistics for each thread. + */ +typedef struct thread_tag { + int thread_num; + pthread_t thread_id; + int r_collisions; + int w_collisions; + int updates; + int interval; +} thread_t; + +/* + * Read-write lock and shared data + */ +typedef struct data_tag { + rwlock_t lock; + int data; + int updates; +} data_t; + +thread_t threads[THREADS]; +data_t data[DATASIZE]; + +/* + * Thread start routine that uses read-write locks + */ +void *thread_routine (void *arg) +{ + thread_t *self = (thread_t*)arg; + int iteration; + int element; + int status; + + element = 0; /* Current data element */ + + for (iteration = 0; iteration < ITERATIONS; iteration++) { + if ((iteration % self->interval) == 0) { + status = rwl_writetrylock (&data[element].lock); + if (status == EBUSY) + self->w_collisions++; + else if (status == 0) { + data[element].data++; + data[element].updates++; + self->updates++; + rwl_writeunlock (&data[element].lock); + } else + err_abort (status, "Try write lock"); + } else { + status = rwl_readtrylock (&data[element].lock); + if (status == EBUSY) + self->r_collisions++; + else if (status != 0) { + err_abort (status, "Try read lock"); + } else { + if (data[element].data != data[element].updates) + printf ("%d: data[%d] %d != %d\n", + self->thread_num, element, + data[element].data, data[element].updates); + rwl_readunlock (&data[element].lock); + } + } + + element++; + if (element >= DATASIZE) + element = 0; + } + return NULL; +} + +int main (int argc, char *argv[]) +{ + int count, data_count; + unsigned int seed = 1; + int thread_updates = 0, data_updates = 0; + int status; + +#ifdef sun + /* + * On Solaris 2.5, threads are not timesliced. To ensure + * that our threads can run concurrently, we need to + * increase the concurrency level to THREADS. + */ + DPRINTF (("Setting concurrency level to %d\n", THREADS)); + thr_setconcurrency (THREADS); +#endif + + /* + * Initialize the shared data. + */ + for (data_count = 0; data_count < DATASIZE; data_count++) { + data[data_count].data = 0; + data[data_count].updates = 0; + rwl_init (&data[data_count].lock); + } + + /* + * Create THREADS threads to access shared data. + */ + for (count = 0; count < THREADS; count++) { + threads[count].thread_num = count; + threads[count].r_collisions = 0; + threads[count].w_collisions = 0; + threads[count].updates = 0; + threads[count].interval = rand_r (&seed) % ITERATIONS; + status = pthread_create (&threads[count].thread_id, + NULL, thread_routine, (void*)&threads[count]); + if (status != 0) + err_abort (status, "Create thread"); + } + + /* + * Wait for all threads to complete, and collect + * statistics. + */ + for (count = 0; count < THREADS; count++) { + status = pthread_join (threads[count].thread_id, NULL); + if (status != 0) + err_abort (status, "Join thread"); + thread_updates += threads[count].updates; + printf ("%02d: interval %d, updates %d, " + "r_collisions %d, w_collisions %d\n", + count, threads[count].interval, + threads[count].updates, + threads[count].r_collisions, threads[count].w_collisions); + } + + /* + * Collect statistics for the data. + */ + for (data_count = 0; data_count < DATASIZE; data_count++) { + data_updates += data[data_count].updates; + printf ("data %02d: value %d, %d updates\n", + data_count, data[data_count].data, data[data_count].updates); + rwl_destroy (&data[data_count].lock); + } + + return 0; +} diff --git a/sched_attr.c b/sched_attr.c new file mode 100644 index 0000000..51fa9c6 --- /dev/null +++ b/sched_attr.c @@ -0,0 +1,139 @@ +/* + * sched_attr.c + * + * Demonstrate use of POSIX 1003.1c-1995 thread priority + * scheduling attributes, by creating an attributes object with + * realtime scheduling policy and priority. + * + * Special notes: Although Solaris 2.5 defines + * _POSIX_THREAD_PRIORITY_SCHEDULING, it does not support the + * SCHED_RR policy for threads. + */ +#include +#include +#include +#include "errors.h" + +/* + * Thread start routine. If priority scheduling is supported, + * report the thread's scheduling attributes. + */ +void *thread_routine (void *arg) +{ + int my_policy; + struct sched_param my_param; + int status; + + /* + * If the priority scheduling option is not defined, then we + * can do nothing with the output of pthread_getschedparam, + * so just report that the thread ran, and exit. + */ +#if defined (_POSIX_THREAD_PRIORITY_SCHEDULING) && !defined (sun) + status = pthread_getschedparam ( + pthread_self (), &my_policy, &my_param); + if (status != 0) + err_abort (status, "Get sched"); + printf ("thread_routine running at %s/%d\n", + (my_policy == SCHED_FIFO ? "FIFO" + : (my_policy == SCHED_RR ? "RR" + : (my_policy == SCHED_OTHER ? "OTHER" + : "unknown"))), + my_param.sched_priority); +#else + printf ("thread_routine running\n"); +#endif + return NULL; +} + +int main (int argc, char *argv[]) +{ + pthread_t thread_id; + pthread_attr_t thread_attr; + int thread_policy; + struct sched_param thread_param; + int status, rr_min_priority, rr_max_priority; + + status = pthread_attr_init (&thread_attr); + if (status != 0) + err_abort (status, "Init attr"); + + /* + * If the priority scheduling option is defined, set various scheduling + * parameters. Note that it is particularly important that you remember + * to set the inheritsched attribute to PTHREAD_EXPLICIT_SCHED, or the + * policy and priority that you've set will be ignored! The default + * behavior is to inherit scheduling information from the creating + * thread. + */ +#if defined (_POSIX_THREAD_PRIORITY_SCHEDULING) && !defined (sun) + status = pthread_attr_getschedpolicy ( + &thread_attr, &thread_policy); + if (status != 0) + err_abort (status, "Get policy"); + status = pthread_attr_getschedparam ( + &thread_attr, &thread_param); + if (status != 0) + err_abort (status, "Get sched param"); + printf ( + "Default policy is %s, priority is %d\n", + (thread_policy == SCHED_FIFO ? "FIFO" + : (thread_policy == SCHED_RR ? "RR" + : (thread_policy == SCHED_OTHER ? "OTHER" + : "unknown"))), + thread_param.sched_priority); + + status = pthread_attr_setschedpolicy ( + &thread_attr, SCHED_RR); + if (status != 0) + printf ("Unable to set SCHED_RR policy.\n"); + else { + /* + * Just for the sake of the exercise, we'll use the + * middle of the priority range allowed for + * SCHED_RR. This should ensure that the thread will be + * run, without blocking everything else. Because any + * assumptions about how a thread's priority interacts + * with other threads (even in other processes) are + * nonportable, especially on an implementation that + * defaults to System contention scope, you may have to + * adjust this code before it will work on some systems. + */ + rr_min_priority = sched_get_priority_min (SCHED_RR); + if (rr_min_priority == -1) + errno_abort ("Get SCHED_RR min priority"); + rr_max_priority = sched_get_priority_max (SCHED_RR); + if (rr_max_priority == -1) + errno_abort ("Get SCHED_RR max priority"); + thread_param.sched_priority = + (rr_min_priority + rr_max_priority)/2; + printf ( + "SCHED_RR priority range is %d to %d: using %d\n", + rr_min_priority, + rr_max_priority, + thread_param.sched_priority); + status = pthread_attr_setschedparam ( + &thread_attr, &thread_param); + if (status != 0) + err_abort (status, "Set params"); + printf ( + "Creating thread at RR/%d\n", + thread_param.sched_priority); + status = pthread_attr_setinheritsched ( + &thread_attr, PTHREAD_EXPLICIT_SCHED); + if (status != 0) + err_abort (status, "Set inherit"); + } +#else + printf ("Priority scheduling not supported\n"); +#endif + status = pthread_create ( + &thread_id, &thread_attr, thread_routine, NULL); + if (status != 0) + err_abort (status, "Create thread"); + status = pthread_join (thread_id, NULL); + if (status != 0) + err_abort (status, "Join thread"); + printf ("Main exiting\n"); + return 0; +} diff --git a/sched_thread.c b/sched_thread.c new file mode 100644 index 0000000..0190384 --- /dev/null +++ b/sched_thread.c @@ -0,0 +1,88 @@ +/* + * sched_thread.c + * + * Demonstrate dynamic scheduling policy use. + * + * Special note: This demonstration will fail on Solaris 2.5 + * because it does not implement SCHED_RR. + */ +#include +#include +#include +#include "errors.h" + +#define THREADS 5 + +/* + * Structure describing each thread. + */ +typedef struct thread_tag { + int index; + pthread_t id; +} thread_t; + +thread_t threads[THREADS]; +int rr_min_priority; + +/* + * Thread start routine that will set its own priority + */ +void *thread_routine (void *arg) +{ + thread_t *self = (thread_t*)arg; + int my_policy; + struct sched_param my_param; + int status; + + my_param.sched_priority = rr_min_priority + self->index; + DPRINTF (( + "Thread %d will set SCHED_FIFO, priority %d\n", + self->index, my_param.sched_priority)); + status = pthread_setschedparam ( + self->id, SCHED_RR, &my_param); + if (status != 0) + err_abort (status, "Set sched"); + status = pthread_getschedparam ( + self->id, &my_policy, &my_param); + if (status != 0) + err_abort (status, "Get sched"); + printf ("thread_routine %d running at %s/%d\n", + self->index, + (my_policy == SCHED_FIFO ? "FIFO" + : (my_policy == SCHED_RR ? "RR" + : (my_policy == SCHED_OTHER ? "OTHER" + : "unknown"))), + my_param.sched_priority); + return NULL; +} + +int main (int argc, char *argv[]) +{ + int count, status; + + rr_min_priority = sched_get_priority_min (SCHED_RR); + if (rr_min_priority == -1) { +#ifdef sun + if (errno == ENOSYS) { + fprintf (stderr, "SCHED_RR is not supported.\n"); + exit (0); + } +#endif + errno_abort ("Get SCHED_RR min priority"); + } + for (count = 0; count < THREADS; count++) { + threads[count].index = count; + status = pthread_create ( + &threads[count].id, NULL, + thread_routine, (void*)&threads[count]); + if (status != 0) + err_abort (status, "Create thread"); + } + for (count = 0; count < THREADS; count++) { + status = pthread_join (threads[count].id, NULL); + if (status != 0) + err_abort (status, "Join thread"); + } + printf ("Main exiting\n"); + return 0; +} diff --git a/semaphore_signal.c b/semaphore_signal.c new file mode 100644 index 0000000..6c7422d --- /dev/null +++ b/semaphore_signal.c @@ -0,0 +1,116 @@ +/* + * semaphore_signal.c + * + * Demonstrate use of POSIX semaphores to wake a thread from + * within a signal catching function. + * + * Special notes: This program depends upon the _POSIX_TIMERS + * and _POSIX_SEMAPHORES features of POSIX 1003.1b-1993. It will + * not run (and may not compile) without them. + */ +#include +#include +#include +#include +#include +#include +#include "errors.h" + +sem_t semaphore; + +/* + * Signal catching function. + */ +void signal_catcher (int sig) +{ + if (sem_post (&semaphore) == -1) + errno_abort ("Post semaphore"); +} + +/* + * Thread start routine which waits on the semaphore. + */ +void *sem_waiter (void *arg) +{ + int number = (int)arg; + int counter; + + /* + * Each thread waits 5 times. + */ + for (counter = 1; counter <= 5; counter++) { + while (sem_wait (&semaphore) == -1) { + if (errno != EINTR) + errno_abort ("Wait on semaphore"); + } + printf ("%d waking (%d)...\n", number, counter); + } + return NULL; +} + +int main (int argc, char *argv[]) +{ + int thread_count, status; + struct sigevent sig_event; + struct sigaction sig_action; + sigset_t sig_mask; + timer_t timer_id; + struct itimerspec timer_val; + pthread_t sem_waiters[5]; + +#if !defined(_POSIX_SEMAPHORES) || !defined(_POSIX_TIMERS) +# if !defined(_POSIX_SEMAPHORES) + printf ("This system does not support POSIX semaphores\n"); +# endif +# if !defined(_POSIX_TIMERS) + printf ("This system does not support POSIX timers\n"); +# endif + return -1; +#else + sem_init (&semaphore, 0, 0); + + /* + * Create 5 threads to wait on a semaphore. + */ + for (thread_count = 0; thread_count < 5; thread_count++) { + status = pthread_create ( + &sem_waiters[thread_count], NULL, + sem_waiter, (void*)thread_count); + if (status != 0) + err_abort (status, "Create thread"); + } + + /* + * Set up a repeating timer using signal number SIGRTMIN, + * set to occur every 2 seconds. + */ + sig_event.sigev_value.sival_int = 0; + sig_event.sigev_signo = SIGRTMIN; + sig_event.sigev_notify = SIGEV_SIGNAL; + if (timer_create (CLOCK_REALTIME, &sig_event, &timer_id) == -1) + errno_abort ("Create timer"); + sigemptyset (&sig_mask); + sigaddset (&sig_mask, SIGRTMIN); + sig_action.sa_handler = signal_catcher; + sig_action.sa_mask = sig_mask; + sig_action.sa_flags = 0; + if (sigaction (SIGRTMIN, &sig_action, NULL) == -1) + errno_abort ("Set signal action"); + timer_val.it_interval.tv_sec = 2; + timer_val.it_interval.tv_nsec = 0; + timer_val.it_value.tv_sec = 2; + timer_val.it_value.tv_nsec = 0; + if (timer_settime (timer_id, 0, &timer_val, NULL) == -1) + errno_abort ("Set timer"); + + /* + * Wait for all threads to complete. + */ + for (thread_count = 0; thread_count < 5; thread_count++) { + status = pthread_join (sem_waiters[thread_count], NULL); + if (status != 0) + err_abort (status, "Join thread"); + } + return 0; +#endif +} diff --git a/semaphore_wait.c b/semaphore_wait.c new file mode 100644 index 0000000..e023530 --- /dev/null +++ b/semaphore_wait.c @@ -0,0 +1,82 @@ +/* + * semaphore_wait.c + * + * Demonstrate use of semaphores for synchronization. + */ +#include +#include +#include +#include +#include +#include "errors.h" + +sem_t semaphore; + +/* + * Thread start routine to wait on a semaphore. + */ +void *sem_waiter (void *arg) +{ + long num = (long)arg; + + printf ("Thread %d waiting\n", num); + if (sem_wait (&semaphore) == -1) + errno_abort ("Wait on semaphore"); + printf ("Thread %d resuming\n", num); + return NULL; +} + +int main (int argc, char *argv[]) +{ + int thread_count; + pthread_t sem_waiters[5]; + int status; + +#if !defined(_POSIX_SEMAPHORES) + printf ("This system does not support POSIX semaphores\n"); + return -1; +#else + + if (sem_init (&semaphore, 0, 0) == -1) + errno_abort ("Init semaphore"); + + /* + * Create 5 threads to wait concurrently on the semaphore. + */ + for (thread_count = 0; thread_count < 5; thread_count++) { + status = pthread_create ( + &sem_waiters[thread_count], NULL, + sem_waiter, (void*)thread_count); + if (status != 0) + err_abort (status, "Create thread"); + } + + sleep (2); + + /* + * "Broadcast" the semaphore by repeatedly posting until the + * count of waiters goes to 0. + */ + while (1) { + int sem_value; + + if (sem_getvalue (&semaphore, &sem_value) == -1) + errno_abort ("Get semaphore value"); + if (sem_value >= 0) + break; + printf ("Posting from main: %d\n", sem_value); + if (sem_post (&semaphore) == -1) + errno_abort ("Post semaphore"); + } + + /* + * Wait for all threads to complete. + */ + for (thread_count = 0; thread_count < 5; thread_count++) { + status = pthread_join (sem_waiters[thread_count], NULL); + if (status != 0) + err_abort (status, "Join thread"); + } + return 0; +#endif +} diff --git a/server.c b/server.c new file mode 100644 index 0000000..9f54685 --- /dev/null +++ b/server.c @@ -0,0 +1,328 @@ +/* + * server.c + * + * Demonstrate a client/server threading model. + * + * Special notes: On a Solaris system, call thr_setconcurrency() + * to allow interleaved thread execution, since threads are not + * timesliced. + */ +#include +#include +#include "errors.h" + +#define CLIENT_THREADS 4 /* Number of clients */ + +#define REQ_READ 1 /* Read with prompt */ +#define REQ_WRITE 2 /* Write */ +#define REQ_QUIT 3 /* Quit server */ + +/* + * Internal to server "package" -- one for each request. + */ +typedef struct request_tag { + struct request_tag *next; /* Link to next */ + int operation; /* Function code */ + int synchronous; /* Non-zero if synchronous */ + int done_flag; /* Predicate for wait */ + pthread_cond_t done; /* Wait for completion */ + char prompt[32]; /* Prompt string for reads */ + char text[128]; /* Read/write text */ +} request_t; + +/* + * Static context for the server + */ +typedef struct tty_server_tag { + request_t *first; + request_t *last; + int running; + pthread_mutex_t mutex; + pthread_cond_t request; +} tty_server_t; + +tty_server_t tty_server = { + NULL, NULL, 0, + PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER}; + +/* + * Main program data + */ + +int client_threads; +pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t clients_done = PTHREAD_COND_INITIALIZER; + +/* + * The server start routine. It waits for a request to appear + * in tty_server.requests using the request condition variable. + * It processes requests in FIFO order. If a request is marked + * "synchronous" (synchronous != 0), the server will set done_flag + * and signal the request's condition variable. The client is + * responsible for freeing the request. If the request was not + * synchronous, the server will free the request on completion. + */ +void *tty_server_routine (void *arg) +{ + static pthread_mutex_t prompt_mutex = PTHREAD_MUTEX_INITIALIZER; + request_t *request; + int operation, len; + int status; + + while (1) { + status = pthread_mutex_lock (&tty_server.mutex); + if (status != 0) + err_abort (status, "Lock server mutex"); + + /* + * Wait for data + */ + while (tty_server.first == NULL) { + status = pthread_cond_wait ( + &tty_server.request, &tty_server.mutex); + if (status != 0) + err_abort (status, "Wait for request"); + } + request = tty_server.first; + tty_server.first = request->next; + if (tty_server.first == NULL) + tty_server.last = NULL; + status = pthread_mutex_unlock (&tty_server.mutex); + if (status != 0) + err_abort (status, "Unlock server mutex"); + + /* + * Process the data + */ + operation = request->operation; + switch (operation) { + case REQ_QUIT: + break; + case REQ_READ: + if (strlen (request->prompt) > 0) + printf (request->prompt); + if (fgets (request->text, 128, stdin) == NULL) + request->text[0] = '\0'; + /* + * Because fgets returns the newline, and we don't want it, + * we look for it, and turn it into a null (truncating the + * input) if found. It should be the last character, if it is + * there. + */ + len = strlen (request->text); + if (len > 0 && request->text[len-1] == '\n') + request->text[len-1] = '\0'; + break; + case REQ_WRITE: + puts (request->text); + break; + default: + break; + } + if (request->synchronous) { + status = pthread_mutex_lock (&tty_server.mutex); + if (status != 0) + err_abort (status, "Lock server mutex"); + request->done_flag = 1; + status = pthread_cond_signal (&request->done); + if (status != 0) + err_abort (status, "Signal server condition"); + status = pthread_mutex_unlock (&tty_server.mutex); + if (status != 0) + err_abort (status, "Unlock server mutex"); + } else + free (request); + if (operation == REQ_QUIT) + break; + } + return NULL; +} + +/* + * Request an operation + */ +void tty_server_request ( + int operation, + int sync, + const char *prompt, + char *string) +{ + request_t *request; + int status; + + status = pthread_mutex_lock (&tty_server.mutex); + if (status != 0) + err_abort (status, "Lock server mutex"); + if (!tty_server.running) { + pthread_t thread; + pthread_attr_t detached_attr; + + status = pthread_attr_init (&detached_attr); + if (status != 0) + err_abort (status, "Init attributes object"); + status = pthread_attr_setdetachstate ( + &detached_attr, PTHREAD_CREATE_DETACHED); + if (status != 0) + err_abort (status, "Set detach state"); + tty_server.running = 1; + status = pthread_create (&thread, &detached_attr, + tty_server_routine, NULL); + if (status != 0) + err_abort (status, "Create server"); + + /* + * Ignore an error in destroying the attributes object. + * It's unlikely to fail, there's nothing useful we can + * do about it, and it's not worth aborting the program + * over it. + */ + pthread_attr_destroy (&detached_attr); + } + + /* + * Create and initialize a request structure. + */ + request = (request_t*)malloc (sizeof (request_t)); + if (request == NULL) + errno_abort ("Allocate request"); + request->next = NULL; + request->operation = operation; + request->synchronous = sync; + if (sync) { + request->done_flag = 0; + status = pthread_cond_init (&request->done, NULL); + if (status != 0) + err_abort (status, "Init request condition"); + } + if (prompt != NULL) + strncpy (request->prompt, prompt, 32); + else + request->prompt[0] = '\0'; + if (operation == REQ_WRITE && string != NULL) + strncpy (request->text, string, 128); + else + request->text[0] = '\0'; + + /* + * Add the request to the queue, maintaining the first and + * last pointers. + */ + if (tty_server.first == NULL) { + tty_server.first = request; + tty_server.last = request; + } else { + (tty_server.last)->next = request; + tty_server.last = request; + } + + /* + * Tell the server that a request is available. + */ + status = pthread_cond_signal (&tty_server.request); + if (status != 0) + err_abort (status, "Wake server"); + + /* + * If the request was "synchronous", then wait for a reply. + */ + if (sync) { + while (!request->done_flag) { + status = pthread_cond_wait ( + &request->done, &tty_server.mutex); + if (status != 0) + err_abort (status, "Wait for sync request"); + } + if (operation == REQ_READ) { + if (strlen (request->text) > 0) + strcpy (string, request->text); + else + string[0] = '\0'; + } + status = pthread_cond_destroy (&request->done); + if (status != 0) + err_abort (status, "Destroy request condition"); + free (request); + } + status = pthread_mutex_unlock (&tty_server.mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); +} + +/* + * Client routine -- multiple copies will request server. + */ +void *client_routine (void *arg) +{ + int my_number = (int)arg, loops; + char prompt[32]; + char string[128], formatted[128]; + int status; + + sprintf (prompt, "Client %d> ", my_number); + while (1) { + tty_server_request (REQ_READ, 1, prompt, string); + if (strlen (string) == 0) + break; + for (loops = 0; loops < 4; loops++) { + sprintf ( + formatted, "(%d#%d) %s", my_number, loops, string); + tty_server_request (REQ_WRITE, 0, NULL, formatted); + sleep (1); + } + } + status = pthread_mutex_lock (&client_mutex); + if (status != 0) + err_abort (status, "Lock client mutex"); + client_threads--; + if (client_threads <= 0) { + status = pthread_cond_signal (&clients_done); + if (status != 0) + err_abort (status, "Signal clients done"); + } + status = pthread_mutex_unlock (&client_mutex); + if (status != 0) + err_abort (status, "Unlock client mutex"); + return NULL; +} + +int main (int argc, char *argv[]) +{ + pthread_t thread; + int count; + int status; + +#ifdef sun + /* + * On Solaris 2.5, threads are not timesliced. To ensure + * that our threads can run concurrently, we need to + * increase the concurrency level to CLIENT_THREADS. + */ + DPRINTF (("Setting concurrency level to %d\n", CLIENT_THREADS)); + thr_setconcurrency (CLIENT_THREADS); +#endif + + /* + * Create CLIENT_THREADS clients. + */ + client_threads = CLIENT_THREADS; + for (count = 0; count < client_threads; count++) { + status = pthread_create (&thread, NULL, + client_routine, (void*)count); + if (status != 0) + err_abort (status, "Create client thread"); + } + status = pthread_mutex_lock (&client_mutex); + if (status != 0) + err_abort (status, "Lock client mutex"); + while (client_threads > 0) { + status = pthread_cond_wait (&clients_done, &client_mutex); + if (status != 0) + err_abort (status, "Wait for clients to finish"); + } + status = pthread_mutex_unlock (&client_mutex); + if (status != 0) + err_abort (status, "Unlock client mutex"); + printf ("All clients done\n"); + tty_server_request (REQ_QUIT, 1, NULL, NULL); + return 0; +} diff --git a/sigev_thread.c b/sigev_thread.c new file mode 100644 index 0000000..89806a3 --- /dev/null +++ b/sigev_thread.c @@ -0,0 +1,106 @@ +/* + * sigev_thread.c + * + * Demonstrate use of the SIGEV_THREAD signal mode to handle + * signals by creating a new thread. + * + * Special notes: This program will not compile on Solaris 2.5. + * It will compile on Digital UNIX 4.0 but will not work. + * Digital UNIX 4.0c fixes SIGEV_THREAD, and sources inform me + * that Solaris 2.6 will also fix SIGEV_THREAD. To try this on + * Solaris 2.5, remove the "#ifdef sun" conditionals in main. + */ +#include +#include +#include +#include "errors.h" + +timer_t timer_id; +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t cond = PTHREAD_COND_INITIALIZER; +int counter = 0; + +/* + * Thread start routine to notify the application when the + * timer expires. This routine is run "as if" it were a new + * thread, each time the timer expires. + * + * When the timer has expired 5 times, the main thread will + * be awakened, and will terminate the program. + */ +void +timer_thread (void *arg) +{ + int status; + + status = pthread_mutex_lock (&mutex); + if (status != 0) + err_abort (status, "Lock mutex"); + if (++counter >= 5) { + status = pthread_cond_signal (&cond); + if (status != 0) + err_abort (status, "Signal condition"); + } + status = pthread_mutex_unlock (&mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + + printf ("Timer %d\n", counter); +} + +main() +{ + int status; + struct itimerspec ts; + struct sigevent se; + +#ifdef sun + fprintf ( + stderr, + "This program cannot compile on Solaris 2.5.\n" + "To build and run on Solaris 2.6, remove the\n" + "\"#ifdef sun\" block in main().\n"); +#else + /* + * Set the sigevent structure to cause the signal to be + * delivered by creating a new thread. + */ + se.sigev_notify = SIGEV_THREAD; + se.sigev_value.sival_ptr = &timer_id; + se.sigev_notify_function = timer_thread; + se.sigev_notify_attributes = NULL; + + /* + * Specify a repeating timer that fires each 5 seconds. + */ + ts.it_value.tv_sec = 5; + ts.it_value.tv_nsec = 0; + ts.it_interval.tv_sec = 5; + ts.it_interval.tv_nsec = 0; + + DPRINTF (("Creating timer\n")); + status = timer_create(CLOCK_REALTIME, &se, &timer_id); + if (status == -1) + errno_abort ("Create timer"); + + DPRINTF (( + "Setting timer %d for 5-second expiration...\n", timer_id)); + status = timer_settime(timer_id, 0, &ts, 0); + if (status == -1) + errno_abort ("Set timer"); + + status = pthread_mutex_lock (&mutex); + if (status != 0) + err_abort (status, "Lock mutex"); + while (counter < 5) { + status = pthread_cond_wait (&cond, &mutex); + if (status != 0) + err_abort (status, "Wait on condition"); + } + status = pthread_mutex_unlock (&mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + +#endif /* Sun */ + return 0; +} diff --git a/sigwait.c b/sigwait.c new file mode 100644 index 0000000..8d1c203 --- /dev/null +++ b/sigwait.c @@ -0,0 +1,98 @@ +/* + * sigwait.c + * + * Demonstrate use of sigwait() to synchronously handle + * asynchrnous signals within a threaded program. + */ +#include +#include +#include +#include +#include "errors.h" + +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t cond = PTHREAD_COND_INITIALIZER; +int interrupted = 0; +sigset_t signal_set; + +/* + * Wait for the SIGINT signal. When it has occurred 5 times, set + * the "interrupted" flag (the main thread's wait predicate) and + * signal a condition variable. The main thread will exit. + */ +void *signal_waiter (void *arg) +{ + int sig_number; + int signal_count = 0; + int status; + + while (1) { + sigwait (&signal_set, &sig_number); + if (sig_number == SIGINT) { + printf ("Got SIGINT (%d of 5)\n", signal_count+1); + if (++signal_count >= 5) { + status = pthread_mutex_lock (&mutex); + if (status != 0) + err_abort (status, "Lock mutex"); + interrupted = 1; + status = pthread_cond_signal (&cond); + if (status != 0) + err_abort (status, "Signal condition"); + status = pthread_mutex_unlock (&mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + break; + } + } + } + return NULL; +} + +int main (int argc, char *argv[]) +{ + pthread_t signal_thread_id; + int status; + + /* + * Start by masking the "interesting" signal, SIGINT in the + * initial thread. Because all threads inherit the signal mask + * from their creator, all threads in the process will have + * SIGINT masked unless one explicitly unmasks it. The + * semantics of sigwait require that all threads (including + * the thread calling sigwait) have the signal masked, for + * reliable operation. Otherwise, a signal that arrives + * while the sigwaiter is not blocked in sigwait might be + * delivered to another thread. + */ + sigemptyset (&signal_set); + sigaddset (&signal_set, SIGINT); + status = pthread_sigmask (SIG_BLOCK, &signal_set, NULL); + if (status != 0) + err_abort (status, "Set signal mask"); + + /* + * Create the sigwait thread. + */ + status = pthread_create (&signal_thread_id, NULL, + signal_waiter, NULL); + if (status != 0) + err_abort (status, "Create sigwaiter"); + + /* + * Wait for the sigwait thread to receive SIGINT and signal + * the condition variable. + */ + status = pthread_mutex_lock (&mutex); + if (status != 0) + err_abort (status, "Lock mutex"); + while (!interrupted) { + status = pthread_cond_wait (&cond, &mutex); + if (status != 0) + err_abort (status, "Wait for interrupt"); + } + status = pthread_mutex_unlock (&mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + printf ("Main terminating with SIGINT\n"); + return 0; +} diff --git a/susp.c b/susp.c new file mode 100644 index 0000000..243dd56 --- /dev/null +++ b/susp.c @@ -0,0 +1,342 @@ +/* + * susp.c + * + * Demonstrate an implementation of thread suspend and resume + * (similar to the Solaris thr_suspend/thr_continue functions) + * using portable POSIX functions. + * + * Note 1: Use of suspend and resume requires extreme care. You + * can easily deadlock your application by suspending a thread + * that holds some resource -- for example, a thread calling + * printf to print a message may have libc mutexes locked, and + * no other thread will be able to return from printf until the + * suspended thread is resumed. + * + * Note 2: This program is called "susp" rather than "suspend" + * to avoid confusion (by your shell) with the suspend command. + * + * Note 3: This simple program will fail if any thread + * terminates during the test. The value of ITERATIONS must be + * adjusted to a value sufficiently large that the main thread can + * complete its two suspend/continue loops before any + * thread_routine threads terminate. + */ +#include +#include +#include +#include "errors.h" + +#define THREAD_COUNT 20 +#define ITERATIONS 40000 + +unsigned long thread_count = THREAD_COUNT; +unsigned long iterations = ITERATIONS; +pthread_mutex_t the_mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; +volatile int sentinel = 0; +pthread_once_t once = PTHREAD_ONCE_INIT; +pthread_t *array = NULL, null_pthread = {0}; +int bottom = 0; +int inited = 0; + +/* + * Handle SIGUSR1 in the target thread, to suspend it until + * receiving SIGUSR2 (resume). + */ +void +suspend_signal_handler (int sig) +{ + sigset_t signal_set; + + /* + * Block all signals except SIGUSR2 while suspended. + */ + sigfillset (&signal_set); + sigdelset (&signal_set, SIGUSR2); + sentinel = 1; + sigsuspend (&signal_set); + + /* + * Once I'm here, I've been resumed, and the resume signal + * handler has been run to completion. + */ + return; +} + +/* + * Handle SIGUSR2 in the target thread, to resume it. Note that + * the signal handler does nothing. It exists only because we need + * to cause sigsuspend() to return. + */ +void +resume_signal_handler (int sig) +{ + return; +} + +/* + * Dynamically initialize the "suspend package" when first used + * (called by pthread_once). + */ +void +suspend_init_routine (void) +{ + int status; + struct sigaction sigusr1, sigusr2; + + /* + * Allocate the suspended threads array. This array is used + * to guarentee idempotency + */ + bottom = 10; + array = (pthread_t*) calloc (bottom, sizeof (pthread_t)); + + /* + * Install the signal handlers for suspend/resume. + */ + sigusr1.sa_flags = 0; + sigusr1.sa_handler = suspend_signal_handler; + + sigemptyset (&sigusr1.sa_mask); + sigusr2.sa_flags = 0; + sigusr2.sa_handler = resume_signal_handler; + sigusr2.sa_mask = sigusr1.sa_mask; + + status = sigaction (SIGUSR1, &sigusr1, NULL); + if (status == -1) + errno_abort ("Installing suspend handler"); + + status = sigaction (SIGUSR2, &sigusr2, NULL); + if (status == -1) + errno_abort ("Installing resume handler"); + + inited = 1; + return; +} + +/* + * Suspend a thread by sending it a signal (SIGUSR1), which will + * block the thread until another signal (SIGUSR2) arrives. + * + * Multiple calls to thd_suspend for a single thread have no + * additional effect on the thread -- a single thd_continue + * call will cause it to resume execution. + */ +int +thd_suspend (pthread_t target_thread) +{ + int status; + int i = 0; + + /* + * The first call to thd_suspend will initialize the + * package. + */ + status = pthread_once (&once, suspend_init_routine); + if (status != 0) + return status; + + /* + * Serialize access to suspend, makes life easier + */ + status = pthread_mutex_lock (&mut); + if (status != 0) + return status; + + /* + * Threads that are suspended are added to the target_array; + * a request to suspend a thread already listed in the array + * is ignored. Sending a second SIGUSR1 would cause the + * thread to re-suspend itself as soon as it is resumed. + */ + while (i < bottom) + if (array[i++] == target_thread) { + status = pthread_mutex_unlock (&mut); + return status; + } + + /* + * Ok, we really need to suspend this thread. So, lets find + * the location in the array that we'll use. If we run off + * the end, realloc the array for more space. + */ + i = 0; + while (array[i] != 0) + i++; + + if (i == bottom) { + array = (pthread_t*) realloc ( + array, (++bottom * sizeof (pthread_t))); + if (array == NULL) { + pthread_mutex_unlock (&mut); + return errno; + } + + array[bottom] = null_pthread; /* Clear new entry */ + } + + /* + * Clear the sentinel and signal the thread to suspend. + */ + sentinel = 0; + status = pthread_kill (target_thread, SIGUSR1); + if (status != 0) { + pthread_mutex_unlock (&mut); + return status; + } + + /* + * Wait for the sentinel to change. + */ + while (sentinel == 0) + sched_yield (); + + array[i] = target_thread; + + status = pthread_mutex_unlock (&mut); + return status; +} + +/* + * Resume a suspended thread by sending it SIGUSR2 to break + * it out of the sigsuspend() in which it's waiting. If the + * target thread isn't suspended, return with success. + */ +int +thd_continue (pthread_t target_thread) +{ + int status; + int i = 0; + + /* + * Serialize access to suspend, makes life easier + */ + status = pthread_mutex_lock (&mut); + if (status != 0) + return status; + + /* + * If we haven't been initialized, then the thread must be "resumed" + * it couldn't have been suspended! + */ + if (!inited) { + status = pthread_mutex_unlock (&mut); + return status; + } + + /* + * Make sure the thread is in the suspend array. If not, it + * hasn't been suspended (or it has already been resumed) and + * we can just carry on. + */ + while (array[i] != target_thread && i < bottom) + i++; + + if (i >= bottom) { + pthread_mutex_unlock (&mut); + return 0; + } + + /* + * Signal the thread to continue, and remove the thread from + * the suspended array. + */ + status = pthread_kill (target_thread, SIGUSR2); + if (status != 0) { + pthread_mutex_unlock (&mut); + return status; + } + + array[i] = 0; /* Clear array element */ + status = pthread_mutex_unlock (&mut); + return status; +} + +static void * +thread_routine (void *arg) +{ + int number = (int)arg; + int status; + int i; + char buffer[128]; + + for (i = 1; i <= iterations; i++) { + /* + * Every time each thread does 5000 interations, print + * a progress report. + */ + if (i % 2000 == 0) { + sprintf ( + buffer, "Thread %02d: %d\n", + number, i); + write (1, buffer, strlen (buffer)); + } + + sched_yield (); + } + + return (void *)0; +} + +int +main (int argc, char *argv[]) +{ + pthread_t threads[THREAD_COUNT]; + pthread_attr_t detach; + int status; + void *result; + int i; + + status = pthread_attr_init (&detach); + if (status != 0) + err_abort (status, "Init attributes object"); + status = pthread_attr_setdetachstate ( + &detach, PTHREAD_CREATE_DETACHED); + if (status != 0) + err_abort (status, "Set create-detached"); + + for (i = 0; i< THREAD_COUNT; i++) { + status = pthread_create ( + &threads[i], &detach, thread_routine, (void *)i); + if (status != 0) + err_abort (status, "Create thread"); + } + + sleep (2); + + for (i = 0; i < THREAD_COUNT/2; i++) { + printf ("Suspending thread %d.\n", i); + status = thd_suspend (threads[i]); + if (status != 0) + err_abort (status, "Suspend thread"); + } + + printf ("Sleeping ...\n"); + sleep (2); + + for (i = 0; i < THREAD_COUNT/2; i++) { + printf ("Continuing thread %d.\n", i); + status = thd_continue (threads[i]); + if (status != 0) + err_abort (status, "Suspend thread"); + } + + for (i = THREAD_COUNT/2; i < THREAD_COUNT; i++) { + printf ("Suspending thread %d.\n", i); + status = thd_suspend (threads[i]); + if (status != 0) + err_abort (status, "Suspend thread"); + } + + printf ("Sleeping ...\n"); + sleep (2); + + for (i = THREAD_COUNT/2; i < THREAD_COUNT; i++) { + printf ("Continuing thread %d.\n", i); + status = thd_continue (threads[i]); + if (status != 0) + err_abort (status, "Continue thread"); + } + + pthread_exit (NULL); /* Let threads finish */ +} diff --git a/thread.c b/thread.c new file mode 100644 index 0000000..5033b63 --- /dev/null +++ b/thread.c @@ -0,0 +1,31 @@ +/* + * thread.c + * + * Demonstrate a simple thread that writes to stdout while the + * initial thread is blocked in a read from stdin. + */ +#include +#include + +/* + * Thread start routine that writes a message. + */ +void *writer_thread (void *arg) +{ + sleep (5); + printf ("Thread I/O\n"); + return NULL; +} + +int main (int argc, char *argv[]) +{ + pthread_t writer_id; + char *input, buffer[64]; + + pthread_create (&writer_id, NULL, writer_thread, NULL); + input = fgets (buffer, 64, stdin); + if (input != NULL) + printf ("You said %s", buffer); + pthread_exit (NULL); + return 0; +} diff --git a/thread_attr.c b/thread_attr.c new file mode 100644 index 0000000..1917727 --- /dev/null +++ b/thread_attr.c @@ -0,0 +1,69 @@ +/* + * thread_attr.c + * + * Create a thread using a non-default attributes object, + * thread_attr. The thread reports its existence, and exits. The + * attributes object specifies that the thread be created + * detached, and, if the stacksize attribute is supported, the + * thread is given a stacksize twice the minimum value. + */ +#include +#include +#include "errors.h" + +/* + * Thread start routine that reports it ran, and then exits. + */ +void *thread_routine (void *arg) +{ + printf ("The thread is here\n"); + return NULL; +} + +int main (int argc, char *argv[]) +{ + pthread_t thread_id; + pthread_attr_t thread_attr; + struct sched_param thread_param; + size_t stack_size; + int status; + + status = pthread_attr_init (&thread_attr); + if (status != 0) + err_abort (status, "Create attr"); + + /* + * Create a detached thread. + */ + status = pthread_attr_setdetachstate ( + &thread_attr, PTHREAD_CREATE_DETACHED); + if (status != 0) + err_abort (status, "Set detach"); +#ifdef _POSIX_THREAD_ATTR_STACKSIZE + /* + * If supported, determine the default stack size and report + * it, and then select a stack size for the new thread. + * + * Note that the standard does not specify the default stack + * size, and the default value in an attributes object need + * not be the size that will actually be used. Solaris 2.5 + * uses a value of 0 to indicate the default. + */ + status = pthread_attr_getstacksize (&thread_attr, &stack_size); + if (status != 0) + err_abort (status, "Get stack size"); + printf ("Default stack size is %u; minimum is %u\n", + stack_size, PTHREAD_STACK_MIN); + status = pthread_attr_setstacksize ( + &thread_attr, PTHREAD_STACK_MIN*2); + if (status != 0) + err_abort (status, "Set stack size"); +#endif + status = pthread_create ( + &thread_id, &thread_attr, thread_routine, NULL); + if (status != 0) + err_abort (status, "Create thread"); + printf ("Main exiting\n"); + pthread_exit (NULL); + return 0; +} diff --git a/thread_error.c b/thread_error.c new file mode 100644 index 0000000..bd1e185 --- /dev/null +++ b/thread_error.c @@ -0,0 +1,29 @@ +/* + * thread_error.c + * + * Demonstrate detection of errors from a typical POSIX 1003.1c-1995 + * function, pthread_join. + */ +#include +#include +#include + +int main (int argc, char *argv[]) +{ + pthread_t thread; + int status; + + /* + * Attempt to join with an uninitialized thread ID. On most + * implementations, this will return an ESRCH error code. If + * the local (and uninitialized) pthread_t happens to be a valid + * thread ID, it is almost certainly that of the initial thread, + * which is running main(). In that case, your Pthreads + * implementation may either return EDEADLK (self-deadlock), + * or it may hang. If it hangs, quit and try again. + */ + status = pthread_join (thread, NULL); + if (status != 0) + fprintf (stderr, "error %d: %s\n", status, strerror (status)); + return status; +} diff --git a/trylock.c b/trylock.c new file mode 100644 index 0000000..8d8860a --- /dev/null +++ b/trylock.c @@ -0,0 +1,120 @@ +/* + * trylock.c + * + * Demonstrate a simple use of pthread_mutex_trylock. The + * counter_thread updates a shared counter at intervals, and a + * monitor_thread occasionally reports the current value of the + * counter -- but only if the mutex is not already locked by + * counter_thread. + * + * Special notes: On a Solaris system, call thr_setconcurrency() + * to allow interleaved thread execution, since threads are not + * timesliced. + */ +#include +#include "errors.h" + +#define SPIN 10000000 + +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +long counter; +time_t end_time; + +/* + * Thread start routine that repeatedly locks a mutex and + * increments a counter. + */ +void *counter_thread (void *arg) +{ + int status; + int spin; + + /* + * Until end_time, increment the counter each + * second. Instead of just incrementing the counter, it + * sleeps for another second with the mutex locked, to give + * monitor_thread a reasonable chance of running. + */ + while (time (NULL) < end_time) + { + status = pthread_mutex_lock (&mutex); + if (status != 0) + err_abort (status, "Lock mutex"); + for (spin = 0; spin < SPIN; spin++) + counter++; + status = pthread_mutex_unlock (&mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + sleep (1); + } + printf ("Counter is %#lx\n", counter); + return NULL; +} + +/* + * Thread start routine to "monitor" the counter. Every 3 + * seconds, try to lock the mutex and read the counter. If the + * trylock fails, skip this cycle. + */ +void *monitor_thread (void *arg) +{ + int status; + int misses = 0; + + + /* + * Loop until end_time, checking the counter every 3 + * seconds. + */ + while (time (NULL) < end_time) + { + sleep (3); + status = pthread_mutex_trylock (&mutex); + if (status != EBUSY) + { + if (status != 0) + err_abort (status, "Trylock mutex"); + printf ("Counter is %ld\n", counter/SPIN); + status = pthread_mutex_unlock (&mutex); + if (status != 0) + err_abort (status, "Unlock mutex"); + } else + misses++; /* Count "misses" on the lock */ + } + printf ("Monitor thread missed update %d times.\n", misses); + return NULL; +} + +int main (int argc, char *argv[]) +{ + int status; + pthread_t counter_thread_id; + pthread_t monitor_thread_id; + +#ifdef sun + /* + * On Solaris 2.5, threads are not timesliced. To ensure + * that our threads can run concurrently, we need to + * increase the concurrency level to 2. + */ + DPRINTF (("Setting concurrency level to 2\n")); + thr_setconcurrency (2); +#endif + + end_time = time (NULL) + 60; /* Run for 1 minute */ + status = pthread_create ( + &counter_thread_id, NULL, counter_thread, NULL); + if (status != 0) + err_abort (status, "Create counter thread"); + status = pthread_create ( + &monitor_thread_id, NULL, monitor_thread, NULL); + if (status != 0) + err_abort (status, "Create monitor thread"); + status = pthread_join (counter_thread_id, NULL); + if (status != 0) + err_abort (status, "Join counter thread"); + status = pthread_join (monitor_thread_id, NULL); + if (status != 0) + err_abort (status, "Join monitor thread"); + return 0; +} diff --git a/tsd_destructor.c b/tsd_destructor.c new file mode 100644 index 0000000..42fcbf5 --- /dev/null +++ b/tsd_destructor.c @@ -0,0 +1,118 @@ +/* + * tsd_destructor.c + * + * Demonstrate use of thread-specific data destructors. + */ +#include +#include "errors.h" + +/* + * Structure used as value of thread-specific data key. + */ +typedef struct private_tag { + pthread_t thread_id; + char *string; +} private_t; + +pthread_key_t identity_key; /* Thread-specific data key */ +pthread_mutex_t identity_key_mutex = PTHREAD_MUTEX_INITIALIZER; +long identity_key_counter = 0; + +/* + * This routine is called as each thread terminates with a value + * for the thread-specific data key. It keeps track of how many + * threads still have values, and deletes the key when there are + * no more references. + */ +void identity_key_destructor (void *value) +{ + private_t *private = (private_t*)value; + int status; + + printf ("thread \"%s\" exiting...\n", private->string); + free (value); + status = pthread_mutex_lock (&identity_key_mutex); + if (status != 0) + err_abort (status, "Lock key mutex"); + identity_key_counter--; + if (identity_key_counter <= 0) { + status = pthread_key_delete (identity_key); + if (status != 0) + err_abort (status, "Delete key"); + printf ("key deleted...\n"); + } + status = pthread_mutex_unlock (&identity_key_mutex); + if (status != 0) + err_abort (status, "Unlock key mutex"); +} + +/* + * Helper routine to allocate a new value for thread-specific + * data key if the thread doesn't already have one. + */ +void *identity_key_get (void) +{ + void *value; + int status; + + value = pthread_getspecific (identity_key); + if (value == NULL) { + value = malloc (sizeof (private_t)); + if (value == NULL) + errno_abort ("Allocate key value"); + status = pthread_setspecific (identity_key, (void*)value); + if (status != 0) + err_abort (status, "Set TSD"); + } + return value; +} + +/* + * Thread start routine to use thread-specific data. + */ +void *thread_routine (void *arg) +{ + private_t *value; + + value = (private_t*)identity_key_get (); + value->thread_id = pthread_self (); + value->string = (char*)arg; + printf ("thread \"%s\" starting...\n", value->string); + sleep (2); + return NULL; +} + +void main (int argc, char *argv[]) +{ + pthread_t thread_1, thread_2; + private_t *value; + int status; + + /* + * Create the TSD key, and set the reference counter to + * the number of threads that will use it (two thread_routine + * threads plus main). This must be done before creating + * the threads! Otherwise, if one thread runs the key's + * destructor before any other thread uses the key, it will + * be deleted. + * + * Note that there's rarely any good reason to delete a + * thread-specific data key. + */ + status = pthread_key_create (&identity_key, identity_key_destructor); + if (status != 0) + err_abort (status, "Create key"); + identity_key_counter = 3; + value = (private_t*)identity_key_get (); + value->thread_id = pthread_self (); + value->string = "Main thread"; + status = pthread_create (&thread_1, NULL, + thread_routine, "Thread 1"); + if (status != 0) + err_abort (status, "Create thread 1"); + status = pthread_create (&thread_2, NULL, + thread_routine, "Thread 2"); + if (status != 0) + err_abort (status, "Create thread 2"); + pthread_exit (NULL); +} diff --git a/tsd_once.c b/tsd_once.c new file mode 100644 index 0000000..c6f9ada --- /dev/null +++ b/tsd_once.c @@ -0,0 +1,81 @@ +/* + * tsd_once.c + * + * Demonstrate use of pthread_once to initialize something + * exactly once within a multithreaded program. + * + * Note that it is often easier to use a statically initialized + * mutex to accomplish the same result. + */ +#include +#include "errors.h" + +/* + * Structure used as the value for thread-specific data key. + */ +typedef struct tsd_tag { + pthread_t thread_id; + char *string; +} tsd_t; + +pthread_key_t tsd_key; /* Thread-specific data key */ +pthread_once_t key_once = PTHREAD_ONCE_INIT; + +/* + * One-time initialization routine used with the pthread_once + * control block. + */ +void once_routine (void) +{ + int status; + + printf ("initializing key\n"); + status = pthread_key_create (&tsd_key, NULL); + if (status != 0) + err_abort (status, "Create key"); +} + +/* + * Thread start routine that uses pthread_once to dynamically + * create a thread-specific data key. + */ +void *thread_routine (void *arg) +{ + tsd_t *value; + int status; + + status = pthread_once (&key_once, once_routine); + if (status != 0) + err_abort (status, "Once init"); + value = (tsd_t*)malloc (sizeof (tsd_t)); + if (value == NULL) + errno_abort ("Allocate key value"); + status = pthread_setspecific (tsd_key, value); + if (status != 0) + err_abort (status, "Set tsd"); + printf ("%s set tsd value %p\n", arg, value); + value->thread_id = pthread_self (); + value->string = (char*)arg; + value = (tsd_t*)pthread_getspecific (tsd_key); + printf ("%s starting...\n", value->string); + sleep (2); + value = (tsd_t*)pthread_getspecific (tsd_key); + printf ("%s done...\n", value->string); + return NULL; +} + +void main (int argc, char *argv[]) +{ + pthread_t thread1, thread2; + int status; + + status = pthread_create ( + &thread1, NULL, thread_routine, "thread 1"); + if (status != 0) + err_abort (status, "Create thread 1"); + status = pthread_create ( + &thread2, NULL, thread_routine, "thread 2"); + if (status != 0) + err_abort (status, "Create thread 2"); + pthread_exit (NULL); +} diff --git a/workq.c b/workq.c new file mode 100644 index 0000000..4efa2e3 --- /dev/null +++ b/workq.c @@ -0,0 +1,307 @@ +/* + * workq.c + * + * This file implements the interfaces for a "work queue" + * manager. A "manager object" is created with several + * parameters, including the required size of a work queue + * entry, the maximum desired degree of parallelism (number of + * threads to service the queue), and the address of an + * execution engine routine. + * + * The application requests a work queue entry from the manager, + * fills in the application-specific fields, and returns it to + * the queue manager for processing. The manager will create a + * new thread to service the queue if all current threads are + * busy and the maximum level of parallelism has not yet been + * reached. + * + * The manager will dequeue items and present them to the + * processing engine until the queue is empty; at that point, + * processing threads will begin to shut down. (They will be + * restarted when work appears.) + */ +#include +#include +#include +#include "errors.h" +#include "workq.h" + +/* + * Thread start routine to serve the work queue. + */ +static void *workq_server (void *arg) +{ + struct timespec timeout; + workq_t *wq = (workq_t *)arg; + workq_ele_t *we; + int status, timedout; + + /* + * We don't need to validate the workq_t here... we don't + * create server threads until requests are queued (the + * queue has been initialized by then!) and we wait for all + * server threads to terminate before destroying a work + * queue. + */ + DPRINTF (("A worker is starting\n")); + status = pthread_mutex_lock (&wq->mutex); + if (status != 0) + return NULL; + + while (1) { + timedout = 0; + DPRINTF (("Worker waiting for work\n")); + clock_gettime (CLOCK_REALTIME, &timeout); + timeout.tv_sec += 2; + + while (wq->first == NULL && !wq->quit) { + /* + * Server threads time out after spending 2 seconds + * waiting for new work, and exit. + */ + status = pthread_cond_timedwait ( + &wq->cv, &wq->mutex, &timeout); + if (status == ETIMEDOUT) { + DPRINTF (("Worker wait timed out\n")); + timedout = 1; + break; + } else if (status != 0) { + /* + * This shouldn't happen, so the work queue + * package should fail. Because the work queue + * API is asynchronous, that would add + * complication. Because the chances of failure + * are slim, I choose to avoid that + * complication. The server thread will return, + * and allow another server thread to pick up + * the work later. Note that, if this was the + * only server thread, the queue won't be + * serviced until a new work item is + * queued. That could be fixed by creating a new + * server here. + */ + DPRINTF (( + "Worker wait failed, %d (%s)\n", + status, strerror (status))); + wq->counter--; + pthread_mutex_unlock (&wq->mutex); + return NULL; + } + } + DPRINTF (("Work queue: %#lx, quit: %d\n", wq->first, wq->quit)); + we = wq->first; + + if (we != NULL) { + wq->first = we->next; + if (wq->last == we) + wq->last = NULL; + status = pthread_mutex_unlock (&wq->mutex); + if (status != 0) + return NULL; + DPRINTF (("Worker calling engine\n")); + wq->engine (we->data); + free (we); + status = pthread_mutex_lock (&wq->mutex); + if (status != 0) + return NULL; + } + + /* + * If there are no more work requests, and the servers + * have been asked to quit, then shut down. + */ + if (wq->first == NULL && wq->quit) { + DPRINTF (("Worker shutting down\n")); + wq->counter--; + + /* + * NOTE: Just to prove that every rule has an + * exception, I'm using the "cv" condition for two + * separate predicates here. That's OK, since the + * case used here applies only once during the life + * of a work queue -- during rundown. The overhead + * is minimal and it's not worth creating a separate + * condition variable that would be waited and + * signaled exactly once! + */ + if (wq->counter == 0) + pthread_cond_broadcast (&wq->cv); + pthread_mutex_unlock (&wq->mutex); + return NULL; + } + + /* + * If there's no more work, and we wait for as long as + * we're allowed, then terminate this server thread. + */ + if (wq->first == NULL && timedout) { + DPRINTF (("engine terminating due to timeout.\n")); + wq->counter--; + break; + } + } + + pthread_mutex_unlock (&wq->mutex); + DPRINTF (("Worker exiting\n")); + return NULL; +} + +/* + * Initialize a work queue. + */ +int workq_init (workq_t *wq, int threads, void (*engine)(void *arg)) +{ + int status; + + status = pthread_attr_init (&wq->attr); + if (status != 0) + return status; + status = pthread_attr_setdetachstate ( + &wq->attr, PTHREAD_CREATE_DETACHED); + if (status != 0) { + pthread_attr_destroy (&wq->attr); + return status; + } + status = pthread_mutex_init (&wq->mutex, NULL); + if (status != 0) { + pthread_attr_destroy (&wq->attr); + return status; + } + status = pthread_cond_init (&wq->cv, NULL); + if (status != 0) { + pthread_mutex_destroy (&wq->mutex); + pthread_attr_destroy (&wq->attr); + return status; + } + wq->quit = 0; /* not time to quit */ + wq->first = wq->last = NULL; /* no queue entries */ + wq->parallelism = threads; /* max servers */ + wq->counter = 0; /* no server threads yet */ + wq->idle = 0; /* no idle servers */ + wq->engine = engine; + wq->valid = WORKQ_VALID; + return 0; +} + +/* + * Destroy a work queue. + */ +int workq_destroy (workq_t *wq) +{ + int status, status1, status2; + + if (wq->valid != WORKQ_VALID) + return EINVAL; + status = pthread_mutex_lock (&wq->mutex); + if (status != 0) + return status; + wq->valid = 0; /* prevent any other operations */ + + /* + * Check whether any threads are active, and run them down: + * + * 1. set the quit flag + * 2. broadcast to wake any servers that may be asleep + * 4. wait for all threads to quit (counter goes to 0) + * Because we don't use join, we don't need to worry + * about tracking thread IDs. + */ + if (wq->counter > 0) { + wq->quit = 1; + /* if any threads are idling, wake them. */ + if (wq->idle > 0) { + status = pthread_cond_broadcast (&wq->cv); + if (status != 0) { + pthread_mutex_unlock (&wq->mutex); + return status; + } + } + + /* + * Just to prove that every rule has an exception, I'm + * using the "cv" condition for two separate predicates + * here. That's OK, since the case used here applies + * only once during the life of a work queue -- during + * rundown. The overhead is minimal and it's not worth + * creating a separate condition variable that would be + * waited and signalled exactly once! + */ + while (wq->counter > 0) { + status = pthread_cond_wait (&wq->cv, &wq->mutex); + if (status != 0) { + pthread_mutex_unlock (&wq->mutex); + return status; + } + } + } + status = pthread_mutex_unlock (&wq->mutex); + if (status != 0) + return status; + status = pthread_mutex_destroy (&wq->mutex); + status1 = pthread_cond_destroy (&wq->cv); + status2 = pthread_attr_destroy (&wq->attr); + return (status ? status : (status1 ? status1 : status2)); +} + +/* + * Add an item to a work queue. + */ +int workq_add (workq_t *wq, void *element) +{ + workq_ele_t *item; + pthread_t id; + int status; + + if (wq->valid != WORKQ_VALID) + return EINVAL; + + /* + * Create and initialize a request structure. + */ + item = (workq_ele_t *)malloc (sizeof (workq_ele_t)); + if (item == NULL) + return ENOMEM; + item->data = element; + item->next = NULL; + status = pthread_mutex_lock (&wq->mutex); + if (status != 0) { + free (item); + return status; + } + + /* + * Add the request to the end of the queue, updating the + * first and last pointers. + */ + if (wq->first == NULL) + wq->first = item; + else + wq->last->next = item; + wq->last = item; + + /* + * if any threads are idling, wake one. + */ + if (wq->idle > 0) { + status = pthread_cond_signal (&wq->cv); + if (status != 0) { + pthread_mutex_unlock (&wq->mutex); + return status; + } + } else if (wq->counter < wq->parallelism) { + /* + * If there were no idling threads, and we're allowed to + * create a new thread, do so. + */ + DPRINTF (("Creating new worker\n")); + status = pthread_create ( + &id, &wq->attr, workq_server, (void*)wq); + if (status != 0) { + pthread_mutex_unlock (&wq->mutex); + return status; + } + wq->counter++; + } + pthread_mutex_unlock (&wq->mutex); + return 0; +} diff --git a/workq.h b/workq.h new file mode 100644 index 0000000..39a99d9 --- /dev/null +++ b/workq.h @@ -0,0 +1,59 @@ +/* + * workq.h + * + * This header file defines the interfaces for a "work queue" + * manager. A "manager object" is created with several + * parameters, including the required size of a work queue + * entry, the maximum desired degree of parallelism (number of + * threads to service the queue), and the address of an + * execution engine routine. + * + * The application requests a work queue entry from the manager, + * fills in the application-specific fields, and returns it to + * the queue manager for processing. The manager will create a + * new thread to service the queue if all current threads are + * busy and the maximum level of parallelism has not yet been + * reached. + * + * The manager will dequeue items and present them to the + * processing engine until the queue is empty; at that point, + * processing threads will begin to shut down. (They will be + * restarted when work appears.) + */ +#include + +/* + * Structure to keep track of work queue requests. + */ +typedef struct workq_ele_tag { + struct workq_ele_tag *next; + void *data; +} workq_ele_t; + +/* + * Structure describing a work queue. + */ +typedef struct workq_tag { + pthread_mutex_t mutex; + pthread_cond_t cv; /* wait for work */ + pthread_attr_t attr; /* create detached threads */ + workq_ele_t *first, *last; /* work queue */ + int valid; /* set when valid */ + int quit; /* set when workq should quit */ + int parallelism; /* number of threads required */ + int counter; /* current number of threads */ + int idle; /* number of idle threads */ + void (*engine)(void *arg); /* user engine */ +} workq_t; + +#define WORKQ_VALID 0xdec1992 + +/* + * Define work queue functions + */ +extern int workq_init ( + workq_t *wq, + int threads, /* maximum threads */ + void (*engine)(void *)); /* engine routine */ +extern int workq_destroy (workq_t *wq); +extern int workq_add (workq_t *wq, void *data); diff --git a/workq_main.c b/workq_main.c new file mode 100644 index 0000000..90352d1 --- /dev/null +++ b/workq_main.c @@ -0,0 +1,144 @@ +/* + * workq_main.c + * + * Demonstrate a use of work queues. + */ +#include +#include +#include +#include +#include "workq.h" +#include "errors.h" + +#define ITERATIONS 25 + +typedef struct power_tag { + int value; + int power; +} power_t; + +typedef struct engine_tag { + struct engine_tag *link; + pthread_t thread_id; + int calls; +} engine_t; + +pthread_key_t engine_key; /* Keep track of active engines */ +pthread_mutex_t engine_list_mutex = PTHREAD_MUTEX_INITIALIZER; +engine_t *engine_list_head = NULL; +workq_t workq; + +/* + * Thread-specific data destructor routine for engine_key + */ +void destructor (void *value_ptr) +{ + engine_t *engine = (engine_t*)value_ptr; + + pthread_mutex_lock (&engine_list_mutex); + engine->link = engine_list_head; + engine_list_head = engine; + pthread_mutex_unlock (&engine_list_mutex); +} + +/* + * This is the routine called by the work queue servers to + * perform operations in parallel. + */ +void engine_routine (void *arg) +{ + engine_t *engine; + power_t *power = (power_t*)arg; + int result, count; + int status; + + engine = pthread_getspecific (engine_key); + if (engine == NULL) { + engine = (engine_t*)malloc (sizeof (engine_t)); + status = pthread_setspecific ( + engine_key, (void*)engine); + if (status != 0) + err_abort (status, "Set tsd"); + engine->thread_id = pthread_self (); + engine->calls = 1; + } else + engine->calls++; + result = 1; + printf ( + "Engine: computing %d^%d\n", + power->value, power->power); + for (count = 1; count <= power->power; count++) + result *= power->value; + free (arg); +} + +/* + * Thread start routine that issues work queue requests. + */ +void *thread_routine (void *arg) +{ + power_t *element; + int count; + unsigned int seed = (unsigned int)time (NULL); + int status; + + /* + * Loop, making requests. + */ + for (count = 0; count < ITERATIONS; count++) { + element = (power_t*)malloc (sizeof (power_t)); + if (element == NULL) + errno_abort ("Allocate element"); + element->value = rand_r (&seed) % 20; + element->power = rand_r (&seed) % 7; + DPRINTF (( + "Request: %d^%d\n", + element->value, element->power)); + status = workq_add (&workq, (void*)element); + if (status != 0) + err_abort (status, "Add to work queue"); + sleep (rand_r (&seed) % 5); + } + return NULL; +} + +int main (int argc, char *argv[]) +{ + pthread_t thread_id; + engine_t *engine; + int count = 0, calls = 0; + int status; + + status = pthread_key_create (&engine_key, destructor); + if (status != 0) + err_abort (status, "Create key"); + status = workq_init (&workq, 4, engine_routine); + if (status != 0) + err_abort (status, "Init work queue"); + status = pthread_create (&thread_id, NULL, thread_routine, NULL); + if (status != 0) + err_abort (status, "Create thread"); + (void)thread_routine (NULL); + status = pthread_join (thread_id, NULL); + if (status != 0) + err_abort (status, "Join thread"); + status = workq_destroy (&workq); + if (status != 0) + err_abort (status, "Destroy work queue"); + + /* + * By now, all of the engine_t structures have been placed + * on the list (by the engine thread destructors), so we + * can count and summarize them. + */ + engine = engine_list_head; + while (engine != NULL) { + count++; + calls += engine->calls; + printf ("engine %d: %d calls\n", count, engine->calls); + engine = engine->link; + } + printf ("%d engine threads processed %d calls\n", + count, calls); + return 0; +}