From a5ec64177dbb020c327a7f720508c5b732b70e4f Mon Sep 17 00:00:00 2001 From: Hailin Date: Thu, 28 Mar 2013 10:34:22 +0800 Subject: [PATCH] initial import from http://www.informit.com/store/programming-with-posix-threads-9780201633924 --- Makefile | 50 +++++ README | 100 +++++++++ alarm.c | 34 +++ alarm_cond.c | 198 ++++++++++++++++++ alarm_fork.c | 56 +++++ alarm_mutex.c | 175 ++++++++++++++++ alarm_thread.c | 59 ++++++ atfork.c | 130 ++++++++++++ backoff.c | 199 ++++++++++++++++++ barrier.c | 144 +++++++++++++ barrier.h | 42 ++++ barrier_main.c | 115 ++++++++++ cancel.c | 66 ++++++ cancel_async.c | 130 ++++++++++++ cancel_cleanup.c | 100 +++++++++ cancel_disable.c | 68 ++++++ cancel_subcontract.c | 115 ++++++++++ cond.c | 95 +++++++++ cond_attr.c | 34 +++ cond_dynamic.c | 40 ++++ cond_static.c | 27 +++ crew.c | 485 +++++++++++++++++++++++++++++++++++++++++++ errors.h | 49 +++++ flock.c | 84 ++++++++ getlogin.c | 42 ++++ hello.c | 28 +++ inertia.c | 56 +++++ lifecycle.c | 36 ++++ mutex_attr.c | 35 ++++ mutex_dynamic.c | 33 +++ mutex_static.c | 23 ++ once.c | 71 +++++++ pipe.c | 263 +++++++++++++++++++++++ putchar.c | 96 +++++++++ rwlock.c | 276 ++++++++++++++++++++++++ rwlock.h | 49 +++++ rwlock_main.c | 167 +++++++++++++++ rwlock_try_main.c | 156 ++++++++++++++ sched_attr.c | 139 +++++++++++++ sched_thread.c | 88 ++++++++ semaphore_signal.c | 116 +++++++++++ semaphore_wait.c | 82 ++++++++ server.c | 328 +++++++++++++++++++++++++++++ sigev_thread.c | 106 ++++++++++ sigwait.c | 98 +++++++++ susp.c | 342 ++++++++++++++++++++++++++++++ thread.c | 31 +++ thread_attr.c | 69 ++++++ thread_error.c | 29 +++ trylock.c | 120 +++++++++++ tsd_destructor.c | 118 +++++++++++ tsd_once.c | 81 ++++++++ workq.c | 307 +++++++++++++++++++++++++++ workq.h | 59 ++++++ workq_main.c | 144 +++++++++++++ 55 files changed, 6183 insertions(+) create mode 100644 Makefile create mode 100644 README create mode 100644 alarm.c create mode 100644 alarm_cond.c create mode 100644 alarm_fork.c create mode 100644 alarm_mutex.c create mode 100644 alarm_thread.c create mode 100644 atfork.c create mode 100644 backoff.c create mode 100644 barrier.c create mode 100644 barrier.h create mode 100644 barrier_main.c create mode 100644 cancel.c create mode 100644 cancel_async.c create mode 100644 cancel_cleanup.c create mode 100644 cancel_disable.c create mode 100644 cancel_subcontract.c create mode 100644 cond.c create mode 100644 cond_attr.c create mode 100644 cond_dynamic.c create mode 100644 cond_static.c create mode 100644 crew.c create mode 100644 errors.h create mode 100644 flock.c create mode 100644 getlogin.c create mode 100644 hello.c create mode 100644 inertia.c create mode 100644 lifecycle.c create mode 100644 mutex_attr.c create mode 100644 mutex_dynamic.c create mode 100644 mutex_static.c create mode 100644 once.c create mode 100644 pipe.c create mode 100644 putchar.c create mode 100644 rwlock.c create mode 100644 rwlock.h create mode 100644 rwlock_main.c create mode 100644 rwlock_try_main.c create mode 100644 sched_attr.c create mode 100644 sched_thread.c create mode 100644 semaphore_signal.c create mode 100644 semaphore_wait.c create mode 100644 server.c create mode 100644 sigev_thread.c create mode 100644 sigwait.c create mode 100644 susp.c create mode 100644 thread.c create mode 100644 thread_attr.c create mode 100644 thread_error.c create mode 100644 trylock.c create mode 100644 tsd_destructor.c create mode 100644 tsd_once.c create mode 100644 workq.c create mode 100644 workq.h create mode 100644 workq_main.c 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; +}