PreviousNext

Thread Safety

Thread safety involves two issues. The first is blocking behavior. Blocking I/O should block just the thread doing the I/O, not the entire process. The following scenario illustrates the kind of problem that can occur when an application fails to observe this rule:

1. The client side of the application executes a blocking I/O call such as a read( ) from the keyboard.

2. The read( ) sleeps for an indeterminate amount of time. All threads in the client process are blocked.

3. A timer thread in the client RPC runtime, which manages the client side of the RPC protocol, is among the blocked threads. Eventually the server side times the connection out, even though the client application is still running.

The second thread safety issue is reentrancy. Routines that operate on shared objects must have appropriate locking in place. A typical reentrancy problem is as follows:

1. The application invokes a nonreentrant malloc( ).

2. DCE threads interrupts the malloc( ) and the interrupt handler executes a properly reentrant malloc( ). The reentrant malloc( ) examines a lock and incorrectly infers that nobody else is currently doing a malloc( ).

3. Global data governing memory allocation for the process becomes corrupted.

These thread safety issues arise in the following two contexts for DCE applications:

· Even when application code is not itself multithreaded (for example, client code that does not make any explicit pthread API calls), both client and server applications are still multithreaded as a result of threads created by the RPC runtime. While such single-threaded application code need not itself be reentrant, it must still avoid blocking the entire process, and it must take care that any library routines that it calls, which may also be called by runtime-created threads, are reentrant.

· When application code is itself multithreaded (which is the default for server manager code), it must, in addition to obeying the rules above), also be reentrant; all access to shared objects must be protected by locks.

Obviously, providing for the second condition in explicitly multithreaded code is the application's responsibility. The pthread API provides a set of facilities that can be used for this purpose. To provide for the first condition, which applies to all application code, DCE implementations provide a mechanism to make system and library calls thread-safe. This may be implemented either by providing a set of wrappers for unsafe calls or by providing reentrant libraries and a nonblocking kernel threads implementation. Applications must always be built using either the appropriate wrapped calls or linked to the appropriate reentrant libraries.

DCE implementations provide, at the least, via wrappers or some other mechanism, the set of thread-safe calls shown in the table entitled Thread-Safe Calls.

Applications should not assume that a call to any routine not on this list is necessarily thread-safe. Whether other routines are safe to call from a DCE applications depends on the following factors:

· Application code that is single threaded (that has not explicitly created any application threads via calls to the pthread API) need not concern itself about reentrancy of routines not on this list, since all library and system calls made by RPC created threads are included in this list. However, such application code must still take care that no calls it makes will block the entire process.

· Application code that is multithreaded must exercise caution when making any call not on this list. Non-reentrant library calls may be wrapped by the application using pthread_lock_global_np( ), although this practice is discouraged since this call is not portable. The global lock can be used only in limited circumstances; the approach will work only if all threads in an application follow the same rule. Failure to observe these restrictions can lead to deadlocks. Note also that this approach will not work with any call that could block the whole process, for example by making a blocking I/O call.


Thread-Safe Calls

_sleep( ) accept( ) atfork( ) calloc( )
catclose( ) catgets( ) catopen( ) cfree( )
close( ) connect( ) creat( ) ctermid( )
cuserid( ) dup( ) dup2( ) fclose( )
fcntl( ) fdopen( ) fflush( ) fgetc( )
fgets( ) fopen( ) fork( ) fprintf( )
fputc( ) fputs( ) fread( ) free( )
freopen( ) fscanf(~) fseek( ) ftell( )
fwrite( ) getc( ) getchar( ) gets( )
getw( ) isatty( ) malloc( ) mktemp( )
open( ) pclose( ) pipe( ) popen( )
printf( ) putc( ) putchar( ) puts( )
putw( ) read( ) readv( ) realloc( )
recv( ) recvfrom( ) recvmsg( ) rewind( )
scanf( ) select( ) send( ) sendmsg( )
sendto( ) setbuf( ) setbuffer( ) setlinebuf( )
setvbuf( ) sigaction( ) sigwait( ) sleep( )
socket( ) socketpair( ) sprintf( ) sscanf( )
system( ) tempnam( ) tmpfile( ) tmpnam( )
ttyname( ) ttyslot( ) vfprintf( ) vprintf( )
vsprintf( ) write( ) writev( )
What follows is a summary of the thread-safety rules that should be followed when using the pthread facilities. The list is by no means comprehensive; it describes the places where multithreaded applications most frequently go wrong.

· Access to all shared objects should be protected by the appropriate synchronization mechanisms. The pthread global lock is not appropriate for such synchronization.

· Mutexes should be used only to protect resources held for a short period of time. In particular, note that pthread_mutex_lock( ) is not a cancellation point. Resources needing to be held exclusively for a long time should be protected by condition variables rather than mutexes, as this will not inhibit cancelability (see Cancellation Points).

· A shared object should be protected by only one mutex.

· Be sure to use the available thread-safe library calls. These may be available as wrapped routines, via the pthread.h header file, or your implementation may supply reentrant libraries which must be linked with DCE applications.

· Avoid nonwrapped process-blocking system calls, such as wait( ).

· When threads need to acquire more than one mutex at a time, create a locking sequence and require that all threads follow the sequence.

· Do not make any assumptions about the atomicity of operations, as these are unlikely to be portable.

· In general, to avoid priority inversion, when three or more threads of different priorities access a lock, associate a priority with the lock and force any thread to raise its priority to the lock priority before acquiring the lock. Note that the default scheduling policy (SCHED_OTHER) mitigates the effects of priority inversion by giving low-priority threads a chance to execute (and thus release held locks) even when higher-priority threads are eligible to run.

· You may be able to use the global locking call pthread_lock_global_np( ) when calling into libraries not known to be thread safe.

· Use the atfork( ) routine to keep the state of mutexes consistent across calls to fork( ). Note, however, that this routine is not considered portable. Try to create threads rather than processes whenever possible.

· Call pthread_cond_wait( ) from within a predicate loop, as in the following example:

while (test_condition)
pthread_cond_wait();

· Set thread attributes via a thread attributes object before thread creation. Changes to a thread attribute object after a thread has been created will not affect the thread's attributes. A thread can straightforwardly change its own scheduling attributes by calling pthread_set_scheduler( ) and pthread_set_prio( ), but cannot reliably change the attributes of another thread once it has been created.

See Canceling Threads and Signals for specific guidelines relating to cancels and signals.