| Previous | Contents | Index |
The code in Example 5-15 demonstrates:
Test your program after compiling it with the "optimize"
compiler option, to ensure that your program contains the appropriate
exception handler code.
5.9.6 Reraise Caught Exceptions That Are Not Fully Handled
Reraise exceptions that are not fully handled. That is, reraise any exception that you catch, unless your handler has performed the complete recovery action for the error. This rule permits an unhandled exception to propagate to some final default handler that knows how to recover fully.
A corollary of this rule is that CATCH_ALL handlers must always reraise the exceptions they catch because they can catch any exception, including those not explicitly known to your code.
It is important to follow this convention, so that your program does
not stop the propagation of a thread cancelation exception or
thread-exit request exception. DECthreads maps these requests into
exceptions, so that exception handler code can have the opportunity to
handle all exceptional conditions---from access violations to
thread-exit. In some applications it is important to be able to catch
these to preserve an external invariant, such as an on-disk database,
but they must always be reraised so that the thread will terminate
properly.
5.9.7 Avoid Dynamically Allocated Exception Objects
Avoid dynamically allocated exception objects. Local exception objects
should be declared (explicitly or implicitly) as static, and
extern exception objects are acceptable.
5.10 Exceptions Defined by DECthreads
Table 5-1 lists the names of exception objects that are defined by DECthreads and the meaning of each exception.
Exception object names that begin with the prefix pthread_ are raised within the DECthreads runtime environment itself and are not meant to be raised by your program code. Names of exception objects that begin with pthread_exc_ are generic and belong to the DECthreads exception package or represent exceptions raised by the underlying system.
| Exception | Definition |
|---|---|
| pthread_cancel_e | Thread cancelation in progress |
| pthread_exc_aritherr_e | Unhandled floating-point exception signal ("arithmetic error") |
| pthread_exc_decovf_e | Unhandled decimal overflow exception |
| pthread_exc_excpu_e | "cpu-time limit exceeded" |
| pthread_exc_exfilsiz_e | "File size limit exceeded" |
| pthread_exc_exquota_e | Operation failed due to insufficient quota |
| pthread_exc_fltdiv_e | Unhandled floating-point/decimal divide by zero exception |
| pthread_exc_fltovf_e | Unhandled floating-point overflow exception |
| pthread_exc_fltund_e | Unhandled floating-point underflow exception |
| pthread_exc_illaddr_e | Data or object could not be referenced |
| pthread_exc_illinstr_e | Unhandled illegal instruction signal ("illegal instruction") |
| pthread_exc_insfmem_e | Insufficient virtual memory for requested operation |
| pthread_exc_intdiv_e | Unhandled integer divide by zero exception |
| pthread_exc_intovf_e | Unhandled integer overflow exception |
| pthread_exc_nopriv_e | Insufficient privilege for requested operation |
| pthread_exc_privinst_e | Unhandled privileged instruction fault exception |
| pthread_exc_resaddr_e | Unhandled reserved addressing fault exception |
| pthread_exc_resoper_e | Unhandled reserved operand fault exception |
| pthread_exc_SIGABRT_e | Unhandled signal ABORT |
| pthread_exc_SIGBUS_e | Unhandled bus error signal |
| pthread_exc_SIGEMT_e | Unhandled EMT signal |
| pthread_exc_SIGFPE_e | Unhandled floating-point exception signal |
| pthread_exc_SIGILL_e | Unhandled illegal instruction signal |
| pthread_exc_SIGIOT_e | Unhandled IOT signal |
| pthread_exc_SIGPIPE_e | Unhandled broken pipe signal |
| pthread_exc_SIGSEGV_e | Unhandled segmentation violation signal |
| pthread_exc_SIGSYS_e | Unhandled bad system call signal |
| pthread_exc_SIGTRAP_e | Unhandled trace or breakpoint trap signal |
| pthread_exc_subrng_e | Unhandled subscript out of range exception |
| pthread_exc_uninitexc_e | Uninitialized exception raised |
| pthread_exit_e | Thread exiting using pthread_exit() |
| pthread_stackovf_e | Attempted stack overflow was detected |
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 DECthreads.
Currently on Tru64 UNIX systems, your program cannot use the DECthreads CATCH to catch a C++ or Ada exception.
However, in a program that uses DECthreads, C++ object destructors will
run when an exception from any facility, including DECthreads, reaches
that frame. This includes the DECthreads 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 DECthreads exception package
on the operating system environment.
5.12.1 Tru64 UNIX Dependencies
Tru64 UNIX has an architecturally specified exception model that is
used by DECthreads 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 DECthreads Exceptions
On OpenVMS, DECthreads 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 DECthreads exception 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 DECthreads exception handler code.
This requirement also pertains to DECthreads status exceptions. Thus, you cannot use the DECthreads exception 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 DECthreads 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 DECthreads cleanup handler, DECthreads 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. DECthreads propagates the exception by calling LIB$STOP with the new argument array.
When a status exception is reraised, DECthreads 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 unavailable the values of any local variables.
This behavior also has these effects:
For example, output of the following form indicates that some thread incurred an access violation which was propagated as a DECthreads 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 where the exception (in this example, the ACCVIO condition) is originating.
This chapter presents two example programs that use routines in the DECthreads 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 upon their default status-returning mechanism to indicate routine
completion status. Example 6-1 uses the POSIX cleanup handler
mechanism to cleanup from thread cancelation. In contrast,
Example 6-2 uses the DECthreads exception package to capture and
cleanup from thread cancelation and other synchronous fatal error
conditions.
6.1 Prime Number Search Example
Example 6-1 shows the use of DECthreads 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 get 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 DECthreads 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, better to control 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 DECthreads pthread interface routines are used in Example 6-1:
| Example 6-1 C Program Example (Prime Number Search) |
|---|
/*
*
* DECthreads 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 inorder 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");
}
|
| Previous | Next | Contents | Index |