Previous | Contents | Index |
A thread-safe library consists of routines that are coded so that they are safe to be called from applications that use threads. The Threads Library provides the thread-independent services (or tis) interface to help you write efficient, thread-safe code that does not itself use threads.
When called by a single-threaded program, the tis interface provides thread-independent synchronization services that are easy to maintain. For instance, tis routines avoid the use of interlocked instructions and memory barriers.
When called by a multithreaded program, the tis routines also provide full support for Threads Library synchronization.
The guidelines for using the pthread interface
routines also apply to using the corresponding tis
interface routine in a multithreaded environment.
4.1 Features of the tis Interface
Among the key features of the tis interface are:
Implementation of the tis interface library varies by operating system. For more information, see this guide's operating system-specific appendixes.
It is not difficult to create thread-safe code using the
tis interface, and it should be straightforward to
modify existing source code that is not thread-safe to make it
thread-safe.
4.1.1 Reentrant Code Required
Your first consideration is whether the language compiler used in translating the source code produces reentrant code. Most Ada compilers generate inherently reentrant code because Ada supports multithreaded programming. On OpenVMS VAX systems, there are special restrictions on using the VAX Ada compiler to produce code or libraries to be interfaced with the Threads Library. See Section 3.9.4.
Although the C, C++, Pascal, BLISS, FORTRAN and COBOL programming
languages do not support multithreaded programming directly, compilers
for those languages generally create reentrant code.
4.1.2 Performance of tis Interface Routines
Routines in the tis interface are designed to impose
low overhead when called from a single-threaded environment. For
example, locking a mutex is essentially just setting a bit, and
unlocking the mutex clears the bit.
4.1.3 Run-Time Linkage of tis Interface Routines
All operations of tis interface routines require a call into the tis library. During program initialization, the Threads Library automatically revectors the program's run-time linkages to most tis routines. This allows subsequent calls to those routines to use the normal multithreaded (and SMP-safe) operations.
After the revectoring of run-time linkages has occurred, for example, a call to tis_mutex_lock() operates exactly as if pthread_mutex_lock() had been called. Thus, the transition from tis stubs to full Threads Library operation is transparent to library code that uses the tis interface.
The tis interface deliberately provides no way to
determine whether the Threads Library is active within the process.
Thread-safe code should always act as if multiple threads can be active.
4.1.4 Cancelation Points
The following routines in the tis interface are cancelation points:
tis_cond_wait()
tis_testcancel()
However, because the tis interface has no mechanism
for requesting thread cancelation, no cancelation requests are actually
delivered in these routines unless threads are present at run-time.
4.2 Using Mutexes
Like the mutexes available through the other pthread interface, tis mutexes provide synchronization between multiple threads that share resources. In fact, you can statically initialize tis mutexes using the PTHREAD_MUTEX_INITIALIZER macro (see the Threads Library pthread.h header file).
You can assign names to your program's tis mutexes by statically initializing them with the PTHREAD_MUTEX_INITWITHNAME_NP macro.
Unlike static initialization, dynamic initialization of tis mutexes is limited due to the absence of support for mutex attributes objects among tis interface routines. Thus, for example, the tis_mutex_init() routine can create only normal mutexes.
Operations on the global lock are also supported by
tis interface routines. The global lock is a recursive
mutex that is provided by the Threads Library for use by any thread.
Your program can use the global lock without calling the
pthread interface by calling
tis_lock_global()
and
tis_unlock_global()
.
4.3 Using Condition Variables
Tis condition variables behave like condition variables created using the pthread interface. You can initialize them statically using the PTHREAD_COND_INITIALIZER macro.
As for tis mutexes, dynamic initialization of tis condition variables is limited due to the absence of support for condition variable attributes objects among tis interface routines.
A condition variable wait is useful only when there are other threads. Your program can have more than one thread only if the Threads Library multithreading run-time environment is present. In a non-threaded environment, a wait aborts and signaling or broadcasting a tis mutex does nothing.
For code in a thread-safe library that uses a condition variable,
construct its wait predicate so that the code does not actually require
a block on the condition variable when called in a single-threaded
environment. Please see the
tis_io_complete()
and
tis_sync()
reference pages.
4.4 Using Thread-Specific Data
The tis interface routines support the use of
thread-specific data. If code in the process creates keys or sets
thread-specific data values before the multithreading run-time
environment is initialized, those keys and values continue to be
available to your program in the initial thread.
4.5 Using Read-Write Locks
A read-write lock is an object that allows the application to control access to information that can be read concurrently by more than one thread and that needs to be read frequently and written only occasionally. Routines that manipulate the tis interface's read-write lock objects can control access to any shared resource.
For example, in a cache of recently accessed information, many threads can simultaneously examine the cache without conflict. When a thread must update the cache, it must have exclusive access.
Tis read-write locks are completely different from the newer pthread read-write locks. Currently, the latter have no tis equivalent.
Your program can acquire a read-write lock for shared read access or for exclusive write access. An attempt to acquire a read-write lock for read access will block when any thread has already acquired that lock for write access. An attempt to acquire a read-write lock for write access will block when another thread has already acquired that lock for either read or write access.
In a multithreaded environment, when both readers and writers are waiting at the same time for access via an already acquired read-write lock, tis read-write locks give precedence to the readers when the lock is released. This policy of "read precedence" favors concurrency because it potentially allows many threads to accomplish work simultaneously. (Note that this differs from pthread read-write locks, which have writer precedence.) Figure 4-1 shows a read-write lock's behavior in response to three threads (one writer and two readers) that must access the same memory object.
Figure 4-1 Read-Write Lock Behavior
The tis_rwlock_init() routine initializes a read-write lock by initializing the supplied tis_rwlock_t structure.
Your program uses the tis_read_lock() or tis_write_lock() routine to acquire a read-write lock when access to a shared resource is required. tis_read_trylock() and tis_write_trylock() can also be called to acquire a read-write lock. Note that if the lock is already acquired by another caller, tis_read_trylock() and tis_write_trylock() immediately return [EBUSY], rather than waiting.
If a non-threaded program manes a tis call that would block (such as a call to tis_cond_wait() , tis_read_lock() or tis_write_lock() ), it is a fatal error that will abort the program.
Your program calls the tis_rwlock_destroy() routine when it is finished using a read-write lock. This routine frees the lock's resources for re-use.
For more information about each tis interface routine that manipulates a read-write lock, see Part 3.
This chapter describes how to use the exceptions package and demonstrates conventions for the modular use of exceptions in a multithreaded program.
This chapter does the following:
The exceptions package is a part of the POSIX Threads Library. A C
language header file (
pthread_exception.h
) provides an interface for defining and handling exceptions. It is
designed for use with the pthreads interface routines. If you use the
exceptions package, your application must be linked with the Threads
Library.
5.1.1 Supported Programming Languages
You can use the exceptions package only when you are programming in the C language. While the exceptions will compile under C++, they will not behave properly. In addition, gcc lacks the Compaq C extensions that are needed to interact with the native exception handling system, and will not interoperate correctly with other language exception facilities.
You can use the C language exception handling mechanism (SEH) to catch
exceptions. You can catch exceptions in C++ using
catch(...)
, and propagation of exceptions will run C++ object destructors.
Currently, C++ code cannot catch specific exceptions. Also,
CATCH
,
CATCH_ALL
and
FINALLY
clauses will not run when C++ code raises an exception. (These
restrictions will be reduced or removed in a future release.)
5.1.2 Relation of Exceptions to Return Codes and Signals
The Threads Library uses exceptions in the following cases:
An exception is a mechanism for reporting an error condition. An exception is represented by an exception subject. Operations on exception objects allow your program to report and handle errors. If your program can handle an exception properly, the program can recover from errors. For example, while reading a tape, a program raises an exception from a parity error. The recovery action might be to retry reading the tape 100 times before giving up. However, if the program does not handle the exception, then the program terminates. Reporting errors via exceptions ensures that the error will not inadvertently go unnoticed and cause problems later.
You use exception programming to identify a portion of a routine, called an exception scope, where a calling thread wishes to respond to particular error conditions or perhaps to any error condition. The thread can respond to each exception in either of two ways:
As a result, you can use the exceptions package to handle thread
cancelation and thread exit in a unified and modular manner. Because
the Threads Library implements both thread cancelation and thread exit
by raising exceptions, your code can respond to these events in the
same modular manner as it does for error conditions.
5.3 Exception Programming
Each exception object is of the EXCEPTION type, which is defined in the pthread_exception.h header file.
To use exceptions, do the following:
When a thread in your program raises an exception, the Threads Library determines whether an exception scope has been defined in the current stack frame. If so, the Threads Library checks whether there is either a specific handler ( CATCH code block) for the raised exception or an unspecified handler ( CATCH_ALL or FINALLY code block). If not, the Threads Library passes the raised exception to the next outer exception scope that does contain the pertinent code block. Thread execution resumes at that block. Attempting to catch a raised exception can cause a thread's stack to be unwound one or more call frames.
An exception can be caught only by the thread in which it is raised. An
exception does not propagate from one thread to another.
5.3.1 Declaring and Initializing an Exception
Before referring to an exception object in your code, your program must declare and initialize the object. You must define an exception object (whether explicitly or implicitly) to be of static storage class.
The next sample code fragment demonstrates how a program declares and initializes an exception object.
static EXCEPTION parity_error; /* Declare the exception */ EXCEPTION_INIT (parity_error); /* Initialize the exception */ |
Raise an exception to indicate that your program has detected an error condition in response to which the program must take some action. Your program raises the exception by invoking the RAISE macro.
Example 5-1 demonstrates how to raise an exception.
Example 5-1 Raising an Exception |
---|
static EXCEPTION parity_error; int read_tape(void) { int ret; EXCEPTION_INIT (parity_error); /* Initialize it */ if (tape_is_ready) { ret = read(tape_device); if (ret = BAD_PARITY) RAISE (parity_error); /* Raise it */ } } |
After your program raises an exception, it is passed to a location within a block of code in a containing exception scope. The exception scope defines:
Example 5-2 shows a TRY code block with a CATCH code block defined to catch the exception object named parity_error when it is raised within the read_tape() routine.
Example 5-2 Catching an Exception Using CATCH |
---|
TRY { read_tape (); } CATCH (parity_error) { printf ("Oops, parity error, read aborted\n"); printf ("Try cleaning the heads!\n"); } ENDTRY |
Example 5-3 demonstrates how CATCH and CATCH_ALL code blocks work together to handle different raised exceptions within a given TRY code block.
Example 5-3 Catching an Exception Using CATCH and CATCH_ALL |
---|
int *local_mem; local_mem = malloc (sizeof (int)); TRY { /* An exception can be raised within this scope */ read_tape (); free (local_mem); } CATCH (parity_error) { printf ("Oops, parity error, read aborted\n"); printf ("Try cleaning the heads!\n"); free (local_mem); } CATCH_ALL { free (local_mem); RERAISE; } ENDTRY |
Previous | Next | Contents | Index |