Document revision date: 30 March 2001

## Guide to the POSIX Threads Library

Previous | Contents | Index

## 5.11 Interoperability of Language-Specific Exceptions

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:

- The new primary condition code is not available to any `CATCH` blocks in call frames further into the stack, because those blocks trigger based on the status value in the original status exception; however, subsequent `CATCHES` will function properly.
- The status code for the original status exception is available to any "native" OpenVMS condition handler in the argument array as a chained (secondary) OpenVMS condition. You must code such a handler to recognize the CMA$_EXCCOP and CMA$_EXCCOPLOS condition codes and to use the chained condition code when those are encountered as the primary.

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.

## Chapter 6
Examples

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:

- Set up the environment, which means initialize the program's mutexes and one condition variable.
- Create worker threads. Creation of worker threads is straightforward and uses the default attributes.
- Broadcast to the worker threads that they can start.
- Join each thread as it finishes. As the parent joins each of the returning worker threads, it receives an exit value from each that indicates whether that worker thread exited normally. In this case, the exit values on all but one of the worker threads should be --1, indicating that the thread was canceled.
- Sort and print the list of primes.

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 (&current_mutex);
            check (status, "Mutex_lock");
            current_num = current_num + 2;              /* Skip even numbers */
            numerator = current_num;
            status = pthread_mutex_unlock (&current_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.

Asynchronous Commands

The asynchronous commands are `date` and `time` .

The asynchronous commands are as follows:

- `date` delay_number_of_seconds
  Waits the specified number of seconds before displaying today's date
- `time` delay_number_of_seconds
  Waits the specified number of seconds before displaying the time of day

For example, issuing the following command causes the program to wait 10 seconds before reporting the time:

       Info> time 10

Housekeeping Commands

The housekeeping commands are as follows:

- `status` command_number
  Displays the state of a command
- `wait` command_number
  Waits for a command to finish
- `cancel` command_number
  Stops a command

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:

- The `main()` routine uses `pthread_once()` to perform one-time initialization.
- The `do_delay()` routine specifies the preset delay interval. For a `timespec` structure, initializing `tv.sec = 1` and `tv.nsec = 0` results in a delay of one second.
- The `do_cleanup()` and `find_free_thread()` routines must lock two mutexes at the same time. To avoid deadlock, each routine in the program must lock the two mutexes in the same order.
- The `find_free_thread()` routine uses `pthread_detach()` to detach the free thread found because no other threads will join it.

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

| privacy and legal statement |
6101PRO_012.HTML