Previous | Contents | Index |
In general, the parts of your program that are coded in a given language (C, C++, Ada) can use only that language's own exception objects. This is also true for a program that uses the Threads Library.
Currently on Tru64 UNIX systems, your program cannot use CATCH to catch a C++ or Ada exception.
However, in a program that uses the Threads Library, C++ object
destructors will run when an exception from any facility, including the
Threads Library, reaches that frame. This includes the exceptions
pthread_cancel_e
(cancelation of thread) and
pthread_exit_e
(thread exit).
5.12 Host Operating System Dependencies
This section mentions dependencies of the exceptions package on the
operating system environment.
5.12.1 Tru64 UNIX Dependencies
Tru64 UNIX has an architecturally specified exception model that is
used by the Threads Library as well as C++, Compaq Ada, and other
languages that support exceptions. The Compaq C compiler has extensions
that allow "native" exception handling.
5.12.2 OpenVMS Conditions and Exceptions
On OpenVMS, the Threads Library propagates exceptions within the context of the OpenVMS Condition Handling Facility (CHF). An exception is typically raised by calling LIB$STOP with one of the condition codes listed in Table B-3.
Like the pthread_cleanup_push() routine, the exceptions package's TRY macro establishes an OpenVMS condition handler that catches conditions of "fatal" or "severe" severity. Conditions with other severity values are passed through and thus cannot be caught using exception handler code.
This requirement also pertains to status exceptions. Thus, you cannot use the exceptions package's CATCH , CATCH_ALL , and FINALLY macros to handle a status exception that is not of "severe" or "fatal" severity.
When your program raises an exception, an OpenVMS condition has been signaled. Until the exception is actually caught (that is, before passing through any TRY blocks or cleanup handlers), the primary condition code is either CMA$_EXCEPTION (for an address exception) or a status value (for a status exception).
When a status exception is reraised, whether performed explicitly in a CATCH or CATCH_ALL block or implicitly at the end of a FINALLY block or a cleanup handler, the Threads Library changes the primary condition code to either CMA$_EXCCOP or CMA$_EXCCOPLOS (depending on whether the contents of the exception can be reliably copied) and chains the original status code to the new primary as a secondary condition code. The Threads Library propagates the exception by calling LIB$STOP with the new argument array.
When a status exception is reraised, the Threads Library changes the primary condition code to indicate, first, that the exception has been reraised and, second, that the state of the program has been altered since the original exception was raised---that is, some number of frames have been unwound from the stack, which makes the values of any local variables unavailable.
This behavior also has these effects:
For example, output of the following form indicates that some thread incurred an access violation that was propagated as an exception without being fully handled.
%CMA-F-EXCCOP, exception raised; VMS condition code follows -SYSTEM-F-ACCVIO, access violation, reason mask=00, virtual address=0000000000000000, PC=000000000002013C, PS=0000001B |
After noticing the location where the access violation occurred, or by running the failing program under the debugger with a breakpoint set on exceptions, you can determine the location from which the exception (in this example, the ACCVIO condition) is originating.
This chapter presents two example programs that use routines in the pthread interface. Example 6-1 utilizes one parent thread and a set of worker threads to perform a prime number search. Example 6-2 implements a simple, text-based, asynchronous user interface that reads and writes commands to the terminal.
Both examples use the pthread interface routines and
rely on their default status-returning mechanism to indicate routine
completion status. Example 6-1 uses the POSIX cleanup handler
mechanism to clean up from thread cancelation. In contrast,
Example 6-2 uses the exception package to capture and clean up from
thread cancelation and other synchronous fatal error conditions.
6.1 Prime Number Search Example
Example 6-1 shows the use of pthread interface routines in a C program that performs a prime number search. The program finds a specified number of prime numbers, then sorts and displays these numbers. Several threads participate in the search: each thread takes a number (the next one to be checked), checks whether it is a prime, records it if it is prime, and then takes another number, and so on.
This program reflects the work crew functional model (see Section 1.4.2.) The worker threads increment the integer variable current_num to obtain their next work assignment. As a whole, the worker threads are responsible for finding a specified number of prime numbers, at which point their work is complete.
The number of worker threads to use and the number of prime numbers to find are defined as constants. A macro checks for an error status from each call to the Threads Library and prints a given string and the associated error value. Data that is accessed by all threads (mutexes, condition variables, and so on) are declared as global items.
Each worker thread executes the prime_search() routine, which immediately waits for permission to continue from the parent thread. The worker thread synchronizes with the parent thread using a predicate and a condition variable. Before and after waiting on the condition variable, each worker thread pushes and pops, respectively, a cleanup handler routine ( unlock_cond() ) to allow recovery from cancelation or other unexpected thread exit.
Notice that a predicate loop encloses the condition wait, to prevent the worker thread from continuing if it is wrongly signaled or broadcast. The lock associated with the condition variable must be held by the thread during the call to condition wait. The lock is released within the call and acquired again upon being signaled or broadcast. Note that the same mutex must be used for all operations performed on a specific condition variable.
After the parent sets the predicate and broadcasts, each worker thread begins finding prime numbers until canceled by a fellow worker who has found the last requested prime number. Upon each iteration a given worker increments the current number to examine and takes that new value as its next work item. Each worker thread uses a mutex to access the next work item, to ensure that no two threads are working on the same item. This type of locking protocol should be performed on all global data to ensure its integrity.
Next, each worker thread determines whether its current work item is prime by trying to divide numbers into it. If the number proves to be nondivisible, it is put on the list of primes. The worker thread disables its own cancelability while working with the list of primes, to control more easily any cancelation requests that might occur. The list of primes and its current count are protected by mutexes, which also protect the step of canceling all other worker threads upon finding the last requested prime. While the prime list mutex's remains locked, the worker checks whether it has found the last requested prime, and, if so, unsets a predicate and cancels all other worker threads. Finally, the worker enables its own cancelability.
The canceling thread should fall out of the work loop as a result of the predicate that it unsets.
The parent thread's flow of execution is as follows:
The following pthread interface routines are used in Example 6-1:
pthread_cancel()
pthread_cleanup_pop()
pthread_cleanup_push()
pthread_cond_wait()
pthread_create()
pthread_join()
pthread_mutex_lock()
pthread_mutex_unlock()
pthread_setcancelstate()
pthread_testcancel()
Example 6-1 C Program Example (Prime Number Search) |
---|
/* * * example program conducting a prime number search * */ #include <pthread.h> #include <stdio.h> #include <stdlib.h> /* * Constants used by the example. */ #define workers 5 /* Threads to perform prime check */ #define request 110 /* Number of primes to find */ /* * Macros */ #define check(status,string) if (status != 0) { \ errno = status; \ fprintf (stderr, "%s status %d: %s\n", status, string, strerror (status)); \ } /* * Global data */ pthread_mutex_t prime_list = PTHREAD_MUTEX_INITIALIZER; /* Mutex for use in accessing the prime */ pthread_mutex_t current_mutex = PTHREAD_MUTEX_INITIALIZER; /* Mutex associated with current number */ pthread_mutex_t cond_mutex = PTHREAD_MUTEX_INITIALIZER; /* Mutex used for thread start */ pthread_cond_t cond_var = PTHREAD_COND_INITIALIZER; /* Condition variable for thread start */ int current_num= -1;/* Next number to be checked, start odd */ int thread_hold=1; /* Number associated with condition state */ int count=0; /* Count of prime numbers - index to primes */ int primes[request];/* Store prime numbers - synchronize access */ pthread_t threads[workers]; /* Array of worker threads */ static void unlock_cond (void* arg) { int status; /* Hold status from pthread calls */ status = pthread_mutex_unlock (&cond_mutex); check (status, "Mutex_unlock"); } /* * Worker thread routine. * * Worker threads start with this routine, which begins with a condition wait * designed to synchronize the workers and the parent. Each worker thread then * takes a turn taking a number for which it will determine whether or not it * is prime. */ void * prime_search (void* arg) { int numerator; /* Used to determine primeness */ int denominator; /* Used to determine primeness */ int cut_off; /* Number being checked div 2 */ int notifiee; /* Used during a cancelation */ int prime; /* Flag used to indicate primeness */ int my_number; /* Worker thread identifier */ int status; /* Hold status from pthread calls */ int not_done=1; /* Work loop predicate */ int oldstate; /* Old cancel state */ my_number = (int)arg; /* * Synchronize threads and the parent using a condition variable, the * predicate of which (thread_hold) will be set by the parent. */ status = pthread_mutex_lock (&cond_mutex); check (status, "Mutex_lock"); pthread_cleanup_push (unlock_cond, NULL); while (thread_hold) { status = pthread_cond_wait (&cond_var, &cond_mutex); check (status, "Cond_wait"); } pthread_cleanup_pop (1); /* * Perform checks on ever larger integers until the requested * number of primes is found. */ while (not_done) { /* Test for cancelation request */ pthread_testcancel (); /* Get next integer to be checked */ status = pthread_mutex_lock (¤t_mutex); check (status, "Mutex_lock"); current_num = current_num + 2; /* Skip even numbers */ numerator = current_num; status = pthread_mutex_unlock (¤t_mutex); check (status, "Mutex_unlock"); /* Only need to divide in half of number to verify not prime */ cut_off = numerator/2 + 1; prime = 1; /* Check for prime; exit if something evenly divides */ for (denominator = 2; ((denominator < cut_off) && (prime)); denominator++) { prime = numerator % denominator; } if (prime != 0) { /* Explicitly turn off all cancels */ pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &oldstate); /* * Lock a mutex and add this prime number to the list. Also, * if this fulfills the request, cancel all other threads. */ status = pthread_mutex_lock (&prime_list); check (status, "Mutex_lock"); if (count < request) { primes[count] = numerator; count++; } else if (count >= request) { not_done = 0; count++; for (notifiee = 0; notifiee < workers; notifiee++) { if (notifiee != my_number) { status = pthread_cancel (threads[notifiee]); check (status, "Cancel"); } } } status = pthread_mutex_unlock (&prime_list); check (status, "Mutex_unlock"); /* Reenable cancelation */ pthread_setcancelstate (oldstate, &oldstate); } pthread_testcancel (); } return arg; } main() { int worker_num; /* Counter used when indexing workers */ void *exit_value; /* Individual worker's return status */ int list; /* Used to print list of found primes */ int status; /* Hold status from pthread calls */ int index1; /* Used in sorting prime numbers */ int index2; /* Used in sorting prime numbers */ int temp; /* Used in a swap; part of sort */ int line_idx; /* Column alignment for output */ /* * Create the worker threads. */ for (worker_num = 0; worker_num < workers; worker_num++) { status = pthread_create ( &threads[worker_num], NULL, prime_search, (void*)worker_num); check (status, "Pthread_create"); } /* * Set the predicate thread_hold to zero, and broadcast on the * condition variable that the worker threads may proceed. */ status = pthread_mutex_lock (&cond_mutex); check (status, "Mutex_lock"); thread_hold = 0; status = pthread_cond_broadcast (&cond_var); check (status, "Cond_broadcast"); status = pthread_mutex_unlock (&cond_mutex); check (status, "Mutex_unlock"); /* * Join each of the worker threads in order to obtain their * summation totals, and to ensure each has completed * successfully. * * Mark thread storage free to be reclaimed upon termination by * detaching it. */ for (worker_num = 0; worker_num < workers; worker_num++) { status = pthread_join (threads[worker_num], &exit_value); check (status, "Pthread_join"); if (exit_value == (void*)worker_num) printf ("Thread %d terminated normally\n", worker_num); else if (exit_value == PTHREAD_CANCELED) printf ("Thread %d was canceled\n", worker_num); else printf ("Thread %d terminated unexpectedly with %#lx\n", worker_num, exit_value); /* * Upon normal termination the exit_value is equivalent to worker_num. */ } /* * Take the list of prime numbers found by the worker threads and * sort them from lowest value to highest. The worker threads work * concurrently; there is no guarantee that the prime numbers * will be found in order. Therefore, a sort is performed. */ for (index1 = 1; index1 < request; index1++) { for (index2 = 0; index2 < index1; index2++) { if (primes[index1] < primes[index2]) { temp = primes[index2]; primes[index2] = primes[index1]; primes[index1] = temp; } } } /* * Print out the list of prime numbers that the worker threads * found. */ printf ("The list of %d primes follows:\n", request); for (list = 0, line_idx = 0; list < request; list++, line_idx++) { if (line_idx >= 10) { printf (",\n"); line_idx = 0; } else if (line_idx > 0) printf (",\t"); printf ("%d", primes[list]); } printf ("\n"); } |
6.2 Asynchronous User Interface Example
Example 6-2 implements a simple, text-based, asynchronous user
interface. It allows you to use the terminal to start multiple commands
that run concurrently and that report their results at the terminal
when complete. You can monitor the status of, or cancel, commands that
are already running.
This C program utilizes pthread interface routines but also uses the exception package to capture and clean up from thread cancelations (and other synchronous fatal errors) as exceptions.
The asynchronous commands are date and time .
The asynchronous commands are as follows:
For example, issuing the following command causes the program to wait 10 seconds before reporting the time:
Info> time 10 |
The housekeeping commands are as follows:
The argument command_number is the number of the command that assigned and displayed when the asynchronous command starts.
This program is limited to four outstanding commands.
Here is a sample of the output that the program produces:
Info> help Commands are formed by a verb and an optional numeric argument. The following commands are available: Cancel <COMMAND> Cancel running command Date <DELAY> Print the date Help Print this text Quit Quit (same as EOF) Status [<COMMAND>] Report on running command Time <DELAY> Print the time Wait <COMMAND> Wait for command to finish <COMMAND> refers to the command number. <DELAY> delays the command execution for some number of seconds. This delay simulates a command task that actually takes some period of time to execute. During this delay, commands may be initiated, queried, and/or canceled. Info> time 5 This is command #0. Info> date 15 This is command #1. (0) At the tone the time will be, 11:19:46. Info> status 1 Command #1: "date", 8 seconds remaining. Info> status 1 Command #1: "date", 5 seconds remaining. Info> time 10 This is command #0. Info> status 0 Command #0: "time", 8 seconds remaining. Info> status 1 Command #1: "date", waiting to print. (1) Today is Tue, 6 Oct 1992. Info> time 3 This is command #0. Info> wait 0 (0) At the tone the time will be, 11:21:26. Info> date 10 This is command #0. Info> cancel 0 (0) Canceled. Info> quit |
The following pthread routines are used in Example 6-2:
pthread_cancel()
pthread_cond_signal()
pthread_cond_wait()
pthread_create()
pthread_delay_np()
pthread_detach()
pthread_exc_report_np()
pthread_join()
pthread_mutex_init()
pthread_mutex_lock()
pthread_mutex_unlock()
pthread_once()
sched_yield()
In the program source, notice that:
Example 6-2 C Program Example (Asynchronous User Interface) |
---|
/* * * example program featuring an asynchronous user interface * */ /* * Include files */ #include <pthread.h> #include <pthread_exception.h> #include <stdio.h> #include <time.h> #define check(status,string) if (status != 0) { \ fprintf (stderr, "%s status %d: %s\n", string, status, strerror (status)); \ } /* * Local definitions */ #define PROMPT "Info> " /* Prompt string */ #define MAXLINSIZ 81 /* Command line size */ #define THDNUM 5 /* Number of server threads */ /* * Server thread "states" */ #define ST_INIT 0 /* "Initial" state (no thread) */ #define ST_FINISHED 1 /* Command completed */ #define ST_CANCELED 2 /* Command was canceled */ #define ST_ERROR 3 /* Command was terminated by an error */ #define ST_RUNNING 4 /* Command is running */ #ifndef FALSE /* Just in case these are not defined */ # define FALSE 0 # define TRUE (!FALSE) #endif #ifndef NULL /* Just in case this is not defined */ # define NULL ((void*)0) #endif /* * Global variables */ struct THREAD_DATA { pthread_t thread; /* Server thread handle */ pthread_mutex_t mutex; /* Mutex to protect fields below */ int time; /* Amount of delay remaining */ char task; /* Task being performed ('t' or 'd') */ int state; /* State of the server thread */ } thread_data[THDNUM]; pthread_mutex_t free_thread_mutex = /* Mutex to protect "free_thread" */ PTHREAD_MUTEX_INITIALIZER; pthread_cond_t free_thread_cv = /* Condition variable for same */ PTHREAD_COND_INITIALIZER; int free_thread; /* Flag indicating a free thread */ /* * Local Routines */ static void dispatch_task (void *(*routine)(void*), char task, int time); static void do_cancel (int index); static void do_cleanup (int index, int final_state); static void* do_date (void* arg); static void do_delay (int index); static void do_status (int index); static void* do_time (void* arg); static void do_wait (int index); static int find_free_thread (int *index); static char * get_cmd (char *buffer, int size); static int get_y_or_n (char *query, char defans); static void init_routine (void); static void print_help (void); /* * The main program: */ main() { int done = FALSE; /* Flag indicating user is "done" */ char cmdline[MAXLINSIZ]; /* Command line */ char cmd_wd[MAXLINSIZ]; /* Command word */ int cmd_arg; /* Command argument */ int cmd_cnt; /* Number of items on command line */ int status; void *(*routine)(void*); /* Routine to execute in a thread */ static pthread_once_t once_block = PTHREAD_ONCE_INIT; /* * Perform program initialization. */ status = pthread_once (&once_block, init_routine); check (status, "Pthread_once"); /* * Main command loop */ do { /* * Get and parse a command. Yield first so that any threads waiting * to execute get a chance to before we take out the global lock * and block for I/O. */ sched_yield (); if (get_cmd(cmdline, sizeof (cmdline))) { cmd_cnt = sscanf (cmdline, "%s %d", cmd_wd, &cmd_arg); routine = NULL; /* No routine yet */ if ((cmd_cnt == 1) || (cmd_cnt == 2)) { /* Normal result */ cmd_wd[0] = tolower(cmd_wd[0]); /* Map to lower case */ switch (cmd_wd[0]) { case 'h': /* "Help" */ case '?': print_help(); break; case 'q': /* "Quit" */ done = TRUE; break; case 's': /* "Status" */ do_status ((cmd_cnt == 2 ? cmd_arg : -1)); break; /* * These commands require an argument */ case 'c': /* "Cancel" */ case 'd': /* "Date" */ case 't': /* "Time" */ case 'w': /* "Wait" */ if (cmd_cnt != 2) printf ("Missing command argument.\n"); else { switch (cmd_wd[0]) { case 'c': /* "Cancel" */ do_cancel (cmd_arg); break; case 'd': /* "Date" */ routine = do_date; break; case 't': /* "Time" */ routine = do_time; break; case 'w': /* "Wait" */ do_wait (cmd_arg); break; } } break; default: printf ("Unrecognized command.\n"); break; } } else if (cmd_cnt != EOF) /* Ignore blank command line */ printf ("Unexpected parse error.\n"); /* * If there is a routine to be executed in a server thread, * create the thread. */ if (routine) dispatch_task (routine, cmd_wd[0], cmd_arg); } else done = TRUE; } while (!done); } /* * Create a thread to handle the user's request. */ static void dispatch_task (void *(*routine)(void*), char task, int time) { int i; /* Index of free thread slot */ int status; if (find_free_thread (&i)) { /* * Record the data for this thread where both the main thread and the * server thread can share it. Lock the mutex to ensure exclusive * access to the storage. */ status = pthread_mutex_lock (&thread_data[i].mutex); check (status, "Mutex_lock"); thread_data[i].time = time; thread_data[i].task = task; thread_data[i].state = ST_RUNNING; status = pthread_mutex_unlock (&thread_data[i].mutex); check (status, "Mutex_unlock"); /* * Create the thread, using the default attributes. The thread will * execute the specified routine and get its data from array slot 'i'. */ status = pthread_create ( &thread_data[i].thread, NULL, routine, (void*)i); check (status, "Pthread_create"); printf ("This is command #%d.\n\n", i); } } /* * Wait for the completion of the specified command. */ static void do_cancel (int index) { int cancelable; int status; if ((index < 0) || (index >= THDNUM)) printf ("Bad command number %d.\n", index); else { status = pthread_mutex_lock (&thread_data[index].mutex); check (status, "Mutex_lock"); cancelable = (thread_data[index].state == ST_RUNNING); status = pthread_mutex_unlock (&thread_data[index].mutex); check (status, "Mutex_unlock"); if (cancelable) { status = pthread_cancel (thread_data[index].thread); check (status, "Pthread_cancel"); } else printf ("Command %d is not active.\n", index); } } /* * Post-task clean-up routine. */ static void do_cleanup (int index, int final_state) { int status; /* * This thread is about to make the change from "running" to "finished", * so lock a mutex to prevent a race condition in which the main thread * sees this thread as finished before it is actually done cleaning up. * * Note that when attempting to lock more than one mutex at a time, * always lock the mutexes in the same order everywhere in the code. * The ordering here is the same as in "find_free_thread". */ status = pthread_mutex_lock (&free_thread_mutex); check (status, "Mutex_lock"); /* * Mark the thread as finished with its task. */ status = pthread_mutex_lock (&thread_data[index].mutex); check (status, "Mutex_lock"); thread_data[index].state = final_state; status = pthread_mutex_unlock (&thread_data[index].mutex); check (status, "Mutex_unlock"); /* * Set the flag indicating that there is a free thread, and signal the * main thread, in case it is waiting. */ free_thread = TRUE; status = pthread_cond_signal (&free_thread_cv); check (status, "Cond_signal"); status = pthread_mutex_unlock (&free_thread_mutex); check (status, "Mutex_unlock"); } /* * Thread routine that prints out the date. * * Synchronize access to ctime as it is not thread-safe (it returns the address * of a static string). Also synchronize access to stdio routines. */ static void* do_date (void* arg) { time_t clock_time; /* Julian time */ char *date_str; /* Pointer to string returned from ctime */ char day[4], month[4], date[3], year[5]; /* Pieces of ctime string */ TRY { /* * Pretend that this task actually takes a long time to perform. */ do_delay ((int)arg); clock_time = time ((time_t *)0); date_str = ctime (&clock_time); sscanf (date_str, "%s %s %s %*s %s", day, month, date, year); printf ("%d) Today is %s, %s %s %s.\n\n", arg, day, date, month, year); } CATCH (pthread_cancel_e) { printf ("%d) Canceled.\n", arg); /* * Perform exit actions */ do_cleanup ((int)arg, ST_CANCELED); RERAISE; } CATCH_ALL { printf ("%d) ", arg); pthread_exc_report_np (THIS_CATCH); /* * Perform exit actions */ do_cleanup ((int)arg, ST_ERROR); RERAISE; } ENDTRY; /* * Perform exit actions (thread was not canceled). */ do_cleanup ((int)arg, ST_FINISHED); /* * All thread routines return a value. This program does not check the * value, however. */ return arg; } /* * Delay routine * * Since the actual tasks that threads do in this program take so little time * to perform, execute a delay to make it seem like they are taking a long * time. Also, this will give the user something of which to query the progress. */ static void do_delay (int index) { static struct timespec interval = {1, 0}; int done; /* Loop exit condition */ int status; while (TRUE) { /* * Decrement the global count, so the main thread can see how much * progress we have made. Keep decrementing as long as the remaining * time is greater than zero. * * Lock the mutex to ensure no conflict with the main thread that * might be reading the time remaining while we are decrementing it. */ status = pthread_mutex_lock (&thread_data[index].mutex); check (status, "Mutex_lock"); done = ((thread_data[index].time--) <= 0); status = pthread_mutex_unlock (&thread_data[index].mutex); check (status, "Mutex_unlock"); /* * Quit if the time is up. */ if (done) break; /* * Wait for one second. */ pthread_delay_np (&interval); } } /* * Print the status of the specified thread. */ static void do_status (int index) { int start, end; /* Range of commands queried */ int i; /* Loop index */ int output = FALSE; /* Flag: produced output */ int status; if ((index < -1) || (index >= THDNUM)) printf ("Bad command number %d.\n", index); else { if (index == -1) start = 0, end = THDNUM; else start = index, end = start + 1; for (i = start; i < end; i++) { status = pthread_mutex_lock (&thread_data[i].mutex); check (status, "Mutex_lock"); if (thread_data[i].state != ST_INIT) { printf ("Command #%d: ", i); switch (thread_data[i].task) { case 't': printf ("\"time\", "); break; case 'd': printf ("\"date\", "); break; default: printf ("[unknown] "); break; } switch (thread_data[i].state) { case ST_FINISHED: printf ("completed"); break; case ST_CANCELED: printf ("canceled"); break; case ST_ERROR: printf ("terminated by error"); break; case ST_RUNNING: if (thread_data[i].time < 0) printf ("waiting to print"); else printf ( "%d seconds remaining", thread_data[i].time); break; default: printf ("Bad thread state.\n"); break; } printf (".\n"); output = TRUE; } status = pthread_mutex_unlock (&thread_data[i].mutex); check (status, "Mutex_unlock"); } if (!output) printf ("No such command.\n"); printf ("\n"); } } /* * Thread routine that prints out the date. */ static void* do_time (void* arg) { time_t clock_time; /* Julian time */ char *date_str; /* Pointer to string returned from ctime */ char time_str[8]; /* Piece of ctime string */ TRY { /* * Pretend that this task actually takes a long time to perform. */ do_delay ((int)arg); clock_time = time ((time_t *)0); date_str = ctime (&clock_time); sscanf (date_str, "%*s %*s %*s %s", time_str); printf ("%d) At the tone the time will be, %s.%c\n\n", arg, time_str, '\007'); } CATCH (pthread_cancel_e) { printf ("%d) Canceled.\n", arg); do_cleanup ((int)arg, ST_CANCELED); RERAISE; } CATCH_ALL { printf ("%d) ", arg); pthread_exc_report_np (THIS_CATCH); do_cleanup ((int)arg, ST_ERROR); RERAISE; } ENDTRY; /* * Perform exit actions (thread was not canceled). */ do_cleanup ((int)arg, ST_FINISHED); /* * All thread routines return a value. This program does not check the * value, however. */ return arg; } /* * Wait for the completion of the specified command. */ static void do_wait (int index) { int status; void *value; if ((index < 0) || (index >= THDNUM)) printf ("Bad command number %d.\n", index); else { status = pthread_join (thread_data[index].thread, &value); check (status, "Pthread_join"); if (value == (void*)index) printf ("Command %d terminated successfully.\n", index); else if (value == PTHREAD_CANCELED) printf ("Command %d was canceled.\n", index); else printf ("Command %d terminated with unexpected value %#lx", index, value); } } /* * Find a free server thread to handle the user's request. * * If a free thread is found, its index is written at the supplied address * and the function returns true. */ static int find_free_thread (int *index) { int i; /* Loop index */ int found; /* Free thread found */ int retry = FALSE; /* Look again for finished threads */ int status; do { /* * We are about to look for a free thread, so prevent the data state * from changing while we are looking. * * Note that when attempting to lock more than one mutex at a time, * always lock the mutexes in the same order everywhere in the code. * The ordering here is the same as in "do_cleanup". */ status = pthread_mutex_lock (&free_thread_mutex); check (status, "Mutex_lock"); /* * Find a slot that does not have a running thread in it. * * Before checking, lock the mutex to prevent conflict with the thread * if it is running. */ for (i = 0, found = FALSE; i < THDNUM; i++) { status = pthread_mutex_lock (&thread_data[i].mutex); check (status, "Mutex_lock"); found = (thread_data[i].state != ST_RUNNING); status = pthread_mutex_unlock (&thread_data[i].mutex); check (status, "Mutex_unlock"); /* * Now that the mutex is unlocked, break out of the loop if the * thread is free. */ if (found) break; } if (found) retry = FALSE; else { retry = get_y_or_n ( "All threads are currently busy, do you want to wait?", 'Y'); if (retry) { /* * All threads were busy when we started looking, so clear * the "free thread" flag. */ free_thread = FALSE; /* * Now wait until some thread finishes and sets the flag */ while (!free_thread) pthread_cond_wait (&free_thread_cv, &free_thread_mutex); } } pthread_mutex_unlock (&free_thread_mutex); } while (retry); if (found) { /* * Request the Threads Library to reclaim its internal storage * for this old thread before we use the handle to create a new one. */ status = pthread_detach (thread_data[i].thread); check (status, "Pthread_detach"); *index = i; } return (found); } /* * Get the next user command. * * Synchronize I/O with other threads to prevent conflicts if the stdio * routines are not thread-safe. */ static char * get_cmd (char *buffer, int size) { printf (PROMPT); return fgets (buffer, size, stdin); } /* * Get a yes or no answer to a query. A "blank" answer uses default answer. * * Returns TRUE for "yes" and FALSE for "no". */ static int get_y_or_n (char *query, char defans) { char buffer[MAXLINSIZ]; /* User's answer */ int answer; /* Boolean equivalent */ int retry = TRUE; /* Ask again? */ do { buffer[0] = '\0'; /* Initialize the buffer */ flockfile (stdout); flockfile (stdin); printf ("%s [%c] ", query, defans); fgets (buffer, sizeof (buffer), stdin); funlockfile (stdin); funlockfile (stdout); if (buffer[0] == '\0') buffer[0] = defans; /* Apply default */ switch (buffer[0]) { case 'y': case 'Y': answer = TRUE; retry = FALSE; break; case 'n': case 'N': answer = FALSE; retry = FALSE; break; default: printf ("Please enter \"Y\" or \"N\".\n"); retry = TRUE; break; } } while (retry); return answer; } /* * Initialization routine; * * Called as a one-time initialization action. */ static void init_routine (void) { int i; for (i = 0; i < THDNUM; i++) { pthread_mutex_init (&thread_data[i].mutex, NULL); thread_data[i].time = 0; thread_data[i].task = '\0'; thread_data[i].state = ST_INIT; } } /* * Print help text. */ static void print_help (void) { printf ("Commands are formed by a verb and optional numeric argument.\n"); printf ("The following commands are available:\n"); printf ("\tCancel\t[command]\tCancel running command\n"); printf ("\tDate\t[delay]\t\tPrint the date\n"); printf ("\tHelp\t\t\tPrint this text\n"); printf ("\tQuit\t\t\tQuit (same as EOF)\n"); printf ("\tStatus\t[command]\tReport on running command\n"); printf ("\tTime\t[delay]\t\tPrint the time\n"); printf ("\tWait\t[command]\tWait for command to finish\n"); printf ("\n[command] refers to the command number.\n");; printf ("[delay] delays command execution for some number of seconds.\n"); printf ("This delay simulates a command task that actually takes some\n"); printf ("period of time to execute. During this delay, commands may be\n"); printf ("initiated, queried, and/or canceled.\n"); } |
Previous | Next | Contents | Index |