[ Previous | Next | Contents | Glossary | Home | Search ]
AIX Version 4.3 General Programming Concepts: Writing and Debugging Programs

Terminating Threads

A thread automatically terminates when it returns from its entry-point routine. A thread can also explicitly terminate itself or terminate any other thread in the process. Because all threads share the same data space, a thread must perform cleanup operations at termination time; cleanup handlers are provided by the threads library for this purpose.

Read the following to learn more about terminating threads:

Exiting a Thread

A process can exit at any time by any thread by calling the exit subroutine. Similarly, a thread can exit at any time by calling the pthread_exit subroutine.

Calling the exit subroutine terminates the entire process, including all its threads. In a multi-threaded program, the exit subroutine should only be used when the entire process needs to be terminated; for example, in the case of an unrecoverable error. The pthread_exit subroutine should be preferred, even for exiting the initial thread.

Calling the pthread_exit subroutine terminates the calling thread. The status parameter is saved by the library and can be further used when joining the terminated thread. Calling the pthread_exit subroutine is similar, but not identical, to returning from the thread's initial routine. The result of returning from the thread's initial routine depends on the thread:

It is recommended always to use the pthread_exit subroutine to exit a thread to avoid implicitly calling the exit subroutine.

Exiting the initial thread (for example by calling the pthread_exit subroutine from the main routine) does not terminate the process. It only terminates the initial thread. If the initial thread is terminated, the process will be terminated when the last thread in it terminates. In this case, the process return code (usually the return value of the main routine or the parameter of the exit subroutine) is 0 if the last thread was detached or 1 otherwise.

The following example is a slightly modified version of our first multi-threaded program. The program displays exactly 10 messages in each language. This is accomplished by calling the pthread_exit subroutine in the main routine after creating the two threads, and creating a loop in the Thread routine.

#include <pthread.h>    /* include file for pthreads - the 1st */
#include <stdio.h>      /* include file for printf()           */
void *Thread(void *string)
{
        int i;
 
        for (i=0; i<10; i++)
                printf("%s\n", (char *)string);
        pthread_exit(NULL);
}
int main()
{
        char *e_str = "Hello!";
        char *f_str = "Bonjour !";
 
        pthread_t e_th;
        pthread_t f_th;
 
        int rc;
 
        rc = pthread_create(&e_th, NULL, Thread, (void *)e_str);
        if (rc)
                exit(-1);
        rc = pthread_create(&f_th, NULL, Thread, (void *)f_str);
        if (rc)
                exit(-1);
        pthread_exit(NULL);
}

It is important to note that the pthread_exit subroutine frees any thread-specific data, including the thread's stack. Any data allocated on the stack becomes invalid, since the stack is freed and the corresponding memory may be reused by another thread. Therefore, thread synchronization objects (mutexes and condition variables) allocated on a thread's stack must be destroyed before the thread calls the pthread_exit subroutine.

Unlike the exit subroutine, the pthread_exit subroutine does not clean up system resources shared among threads. For example, files are not closed by the pthread_exit subroutine, since they may be used by other threads.

Canceling a Thread

The thread cancellation mechanism allows a thread to terminate the execution of any other thread in the process in a controlled manner. The target thread (that is, the one that's being canceled) can hold cancellation requests pending in a number of ways and perform application-specific cleanup processing when the notice of cancellation is acted upon. When canceled, the thread implicitly calls the pthread_exit((void *)-1) subroutine.

The cancellation of a thread is requested by calling the pthread_cancel subroutine. When the call returns, the request has been registered, but the thread may still be running. The call to the pthread_cancel subroutine is unsuccessful only when the specified thread ID is not valid.

Cancelability State and Type

The cancelability state and type of a thread determines the action taken upon receipt of a cancellation request. Each thread controls its own cancelability state and type with the pthread_setcancelstate and pthread_setcanceltype subroutines.

There are two possible cancelability states and two possible cancelability types, leading to three possible cases, as shown in the following table.

Cancelability State Cancelability Type Resulting Case
Disabled Any (the type is ignored) Case 1
Enabled Deferred Case 2
Enabled Asynchronous Case 3

The following discusses the three possible cases.

  1. Disabled cancelability. Any cancellation request is set pending, until the cancelability state is changed or the thread is terminated in another way.

    A thread should disable cancelability only when performing operations that cannot be interrupted. For example, if a thread is performing some complex file save operations (such as an indexed database) and is canceled during the operation, the files may be left in an inconsistent state. To avoid this, the thread should disable cancelability during the file save operations.

  2. Deferred cancelability. Any cancellation request is set pending until the thread reaches the next cancellation point. It is the default cancelability state.

    This cancelability state ensures that a thread can be cancelled, but limits the cancellation to specific moments in the thread's execution, called cancellation points. A thread canceled on a cancellation point leaves the system in a safe state; however, user data may be inconsistent or locks may be held by the canceled thread. To avoid these situations, you may use cleanup handlers or disable cancelability within critical regions. See Using Cleanup Handlers for more information about cleanup handlers.

  3. Asynchronous cancelability. Any cancellation request is acted upon immediately.

    A thread that is asynchronously canceled while holding resources may leave the process, or even the system, in a state from which it is difficult or impossible to recover. See the following for more information about async-cancel safety.

Async-Cancel Safety

A function is said to be async-cancel safe if it is written so that calling the function with asynchronous cancelability enabled does not cause any resource to be corrupted, even if a cancellation request is delivered at any arbitrary instruction.

Any function that gets a resource as a side effect cannot be made async-cancel safe. For example, if the malloc subroutine is called with asynchronous cancelability enabled, it might acquire the resource successfully, but as it was returning to the caller, it could act on a cancellation request. In such a case, the program would have no way of knowing whether the resource was acquired or not.

For this reason, most library routines cannot be considered async-cancel safe. It is recommended not to use asynchronous cancelability unless you are sure only to perform operations that do not hold resources and only to call async-cancel safe library routines.

The following three subroutines are async-cancel safe; they ensure that cancellation will be properly handled, even if asynchronous cancelability is enabled:

An alternative to asynchronous cancelability is to use deferred cancelability and to add explicit cancellation points by calling the pthread_testcancel subroutine (see below).

Cancellation Points

Cancellation points are points inside of certain subroutines where a thread must act on any pending cancellation request if deferred cancelability is enabled. All these subroutines may block the calling thread or compute indefinitely.

An explicit cancellation point can also be created by calling the pthread_testcancel subroutine. This subroutine simply creates a cancellation point. If deferred cancelability is enabled, and if a cancellation request is pending, the request is acted upon and the thread is terminated. Otherwise, the subroutine simply returns.

Other cancellation points occur when calling the following subroutines:

The pthread_mutex_lock and pthread_mutex_trylock subroutines do not provide a cancellation point. If they did, all functions calling these subroutines (and many functions do) would provide a cancellation point. Having too many cancellation points makes programming very difficult, requiring either lots of disabling and restoring of cancelability or extra effort in trying to arrange for reliable cleanup at every possible place. See Using Mutexes for more information about these subroutines.

Cancellation Points

Cancellation points occur when a thread is executing the following functions:

aio_suspend close
creat fcntl
fsync getmsg
getpmsg lockf
mq_receive mq_send
msgrcv msgsnd
msync nanosleep
open pause
poll pread
pthread_cond_timedwait pthread_cond_wait
pthread_join pthread_testcancel
putpmsg pwrite
read readv
select sem_wait
sigpause sigsuspend
sigtimedwait sigwait
sigwaitinfo sleep
system tcdrain
usleep wait
wait3 waitid
waitpid write
writev

A cancellation point may also occur when a thread is executing the following functions:

catclose catgets catopen
closedir closelog ctermid
dbm_close dbm_delete dbm_fetch
dbm_nextkey dbm_open dbm_store
dlclose dlopen endgrent
endpwent fwprintf fwrite
fwscanf getc getc_unlocked
getchar getchar_unlocked getcwd
getdate getgrent getgrgid
getgrgid_r getgrnam getgrnam_r
getlogin getlogin_r popen
printf putc putc_unlocked
putchar putchar_unlocked puts
pututxline putw putwc
putwchar readdir readdir_r
remove rename rewind
endutxent fclose fcntl
fflush fgetc fgetpos
fgets fgetwc fgetws
fopen fprintf fputc
fputs getpwent getpwnam
getpwnam_r getpwuid getpwuid_r
gets getutxent getutxid
getutxline getw getwc
getwchar getwd rewinddir
scanf seekdir semop
setgrent setpwent setutxent
strerror syslog tmpfile
tmpnam ttyname ttyname_r
fputwc fputws fread
freopen fscanf fseek
fseeko fsetpos ftell
ftello ftw glob
iconv_close iconv_open ioctl
lseek mkstemp nftw
opendir openlog pclose
perror ungetc ungetwc
unlink vfprintf vfwprintf
vprintf vwprintf wprintf
wscanf

The side effects of acting upon a cancellation request while suspended during a call of a function is the same as the side effects that may be seen in a single-threaded program when a call to a function is interrupted by a signal and the given function returns [EINTR]. Any such side effects occur before any cancellation cleanup handlers are called.

Whenever a thread has cancelability enabled and a cancellation request has been made with that thread as the target and the thread calls pthread_testcancel, then the cancellation request is acted upon before pthread_testcancel returns. If a thread has cancelability enabled and the thread has an asynchronous cancellation request pending and the thread is suspended at a cancellation point waiting for an event to occur, then the cancellation request will be acted upon. However, if the thread is suspended at a cancellation point and the event that it is waiting for occurs before the cancellation request is acted upon, it is dependent upon the sequence of events whether the cancellation request is acted upon or whether the request remains pending and the thread resumes normal execution.

Cancellation Example

The following example is a variant of our first multi-threaded program. Both "writer" threads are canceled after 10 seconds, and after they have written their message at least 5 times.

#include <pthread.h>    /* include file for pthreads - the 1st */
#include <stdio.h>      /* include file for printf()           */
#include <unistd.h>     /* include file for sleep()            */
void *Thread(void *string)
{
        int i;
        int o_state;
 
        /* disables cancelability */
        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &o_state);
 
        /* writes five messages */
        for (i=0; i<5; i++)
                printf("%s\n", (char *)string);
 
        /* restores cancelability */
        pthread_setcancelstate(o_state, &o_state);
 
        /* writes further */
        while (1)
                printf("%s\n", (char *)string);
        pthread_exit(NULL);
}
int main()
{
        char *e_str = "Hello!";
        char *f_str = "Bonjour !";
 
        pthread_t e_th;
        pthread_t f_th;
 
        int rc;
 
        /* creates both threads */
        rc = pthread_create(&e_th, NULL, Thread, (void *)e_str);
        if (rc)
                return -1;
        rc = pthread_create(&f_th, NULL, Thread, (void *)f_str);
        if (rc)
                return -1;
  
        /* sleeps a while */
        sleep(10);
 
        /* requests cancellation */
        pthread_cancel(e_th);
        pthread_cancel(f_th);
 
        /* sleeps a bit more */
        sleep(10);
        pthread_exit(NULL);
}

Using Cleanup Handlers

Cleanup handlers provide an easy way to implement a portable mechanism for releasing resources and restoring invariants when a thread terminates.

Calling Cleanup Handlers

Cleanup handlers are specific to each thread. A thread can have several cleanup handlers; cleanup handlers are stored in a thread-specific LIFO stack. They are all called in the following cases:

A cleanup handler is pushed onto the cleanup stack, by the pthread_cleanup_push subroutine. The pthread_cleanup_pop subroutine pops the topmost cleanup handler from the stack, and optionally executes it. Use this subroutine when the cleanup handler is no longer needed.

The cleanup handler is a user-defined routine. It has one parameter, a void pointer, specified when calling the pthread_cleanup_push subroutine. You may specify a pointer to some data the cleanup handler needs to perform its operation.

In the following example, a buffer is allocated for performing some operation. With deferred cancelability enabled, the operation may be stopped at any cancellation point. A cleanup handler is established to free the buffer in that case.

/* the cleanup handler */
 
cleaner(void *buffer)
 
{
        free(buffer);
}

/* fragment of another routine */
...
myBuf = malloc(1000);
if (myBuf != NULL) {
        
        pthread_cleanup_push(cleaner, myBuf);
 
        /*
         *       perform any operation using the buffer,
         *       including calls to other functions
         *       and cancellation points
         */
        
        /* pops the handler and frees the buffer in one call */
        pthread_cleanup_pop(1);
}

Using deferred cancelability ensures that the thread will not act on any cancellation request between the buffer allocation and the registration of the cleanup handler, because neither the malloc subroutine nor the pthread_cleanup_push subroutine provides any cancellation point. When popping the cleanup handler, the handler is executed, freeing the buffer. More complex programs may not execute the handler when popping it, because the cleanup handler should be thought of as an emergency exit for the protected portion of code.

Balancing the Push and Pop Operations

The pthread_cleanup_push and pthread_cleanup_pop subroutines should always appear in pairs within the same lexical scope, that is, within the same function and the same statement block. They can be thought of as left and right parentheses enclosing a protected portion of code.

The reason for this rule is that on some systems these subroutines are implemented as macros. The pthread_cleanup_push subroutine is implemented as a left brace, followed by other statements:

#define pthread_cleanup_push(rtm,arg) { \
         /* other statements */

The pthread_cleanup_pop subroutine is implemented as a right brace following other statements:

#define pthread_cleanup_pop(ex) \
         /* other statements */  \
}

Not following the balancing rule for the pthread_cleanup_push and pthread_cleanup_pop subroutines may lead to compiler errors or to unexpected behavior of your programs when porting to other systems.

In AIX, the pthread_cleanup_push and pthread_cleanup_pop subroutines are library routines, and can be unbalanced within the same statement block. However, they must be balanced in the program, since the cleanup handlers are stacked.

Related Information

Understanding Threads.

Threads Basic Operation Overview.

Creating Threads.

List of Threads Basic Operation Subroutines.


[ Previous | Next | Contents | Glossary | Home | Search ]