Updated: 11 December 1998 |
Guide to DECthreads
Previous | Contents | Index |
When your program must call a routine that is not thread safe, your program must ensure serialization and exclusivity of the unsafe routine across all threads in the program.
If a routine is not specifically documented as thread reentrant or
thread safe, you are most safe to assume that it is not safe to use
as-is with your multithreaded program. Never assume that a routine is
fully thread reentrant unless it is expressly documented as such; a
routine can use static data in ways that are not obvious from its
interface. A routine carefully written to be thread reentrant but that
calls some other routine that is not thread safe without proper
protection, is itself not thread safe.
3.9.3.1 Using Mutex Around Call to Unsafe Code
Holding a mutex while calling any unsafe code accomplishes this. All threads and libraries using the routine should use the same mutex. Note that even if two libraries carefully lock a mutex around every call to a given routine, if each library uses a different mutex, the routine is not protected against multiple simultaneous calls from different libraries.
Note that your program might be required to protect a series
of calls, rather than just a single call, to routines that are not
thread safe.
3.9.3.2 Using or Copying Static Data Before Releasing the Mutex
In many cases your program must protect more than just the call itself
to a routine that is not thread safe. Your program must use or copy any
static return values before releasing the mutex that is being held.
3.9.3.3 Using the DECthreads Global Lock
To ensure serialization and exclusivity of the unsafe code, DECthreads provides one global lock that can be used by all threads in a program when calling routines or code that is not thread safe. The global lock allows a thread to acquire the lock recursively, so that you do not need to be concerned if you call a routine that also may acquire the global lock.
Acquire the global lock by calling pthread_lock_global_np(); release the global lock by calling pthread_unlock_global_np().
Because there is only one global lock, you do not need to fully analyze all of the dependencies in unsafe code that your program calls. For example, with private locks to protect unsafe code, one lock might protect calls to the stdio routine, while another protects calls to math routines. However, if stdio next calls a math routine without acquiring the math routine lock, the call is just as unsafe as if no locks were used.
Use the global lock whenever calling unsafe routines. If you are
unsure, assume that a routine is not thread safe unless it is expressly
documented otherwise. All DECthreads routines are thread safe.
3.9.4 Use of Multiple Threads Libraries Not Supported
DECthreads performs user-mode execution context-switching within a process (OpenVMS VAX) or virtual processor (DIGITAL UNIX and OpenVMS Alpha) by exchanging register sets, including the program counter and stack pointer. If any other code within the process also performs this sort of context switch, neither DECthreads nor that other code can ever know which context is active at any time. This can result in, at best, unpredictable behavior---and, at worst, severe errors.
For example, under OpenVMS VAX, the VAX Ada run-time library provides its own tasking package that does not use DECthreads scheduling. Therefore, VAX Ada tasking cannot be used within a process that also uses DECthreads. (This restriction does not exist for DEC Ada for DIGITAL UNIX or for OpenVMS Alpha, because it uses DECthreads.)
This potential confusion might not exist for platforms that offer
kernel thread-only packages. For example, DECthreads for Windows NT
coexists smoothly with that platform's native Win32 threads.
3.10 Detecting DECthreads Error Conditions
DECthreads can detect some of the following types of errors:
API errors are reported in different ways by the various DECthreads interfaces:
DECthreads internal errors result in a bugcheck. DECthreads writes a message that summarizes the problem to the process's current error device, and (on OpenVMS and Windows NT platforms) writes a file that contains more detailed information.
By default, the file is named pthread_dump.log and is created in the process's current (or default) directory. To cause DECthreads to write the bugcheck information into a different file, define PTHREAD_CONFIG and set its dump= major keyword. (See Section D.1 for more information about using PTHREAD_CONFIG.)
If DECthreads cannot create the specified file when it performs the bugcheck, it will try to create the default file. If it cannot create the default file, it will write the detailed information to the error device.
On DIGITAL UNIX systems: DECthreads no longer creates a dump file, because a core file is sufficient for analysis of the process using the Ladebug debugger. |
The header message written to the error device starts with a line reporting that DECthreads has detected an internal problem and that it is terminating execution. It also includes the version of the DECthreads library. The message resembles this:
%DECthreads bugcheck (version V3.13-180), terminating execution. |
The next line states the reason for the failure. On DIGITAL UNIX, this is followed by process termination with SIGABRT (SIGIOT), which causes writing of a core dump file. On other platforms, a final line on the error device specifies the location of the file that contains detailed state information produced by DECthreads, as in the following example:
% Dumping to pthread_dump.log |
The detailed information file contains information that is usually
necessary to track down the problem. If you encounter a DECthreads
bugcheck, please contact your Compaq support representative and include
this information file (or the DIGITAL UNIX core file) along with sample
code and output. Always include the full name and version of the
operating system, and any patches that have been installed. If complete
version information is lacking, useful core file analysis might not be
possible.
3.10.2 Interpreting a DECthreads Bugcheck
The fact that DECthreads terminated the process with a bugcheck can mean that some subtle problem in DECthreads has been uncovered. However, DECthreads does not check for all possible API errors, and there are a number of ways in which incorrect code in your program can lead to a DECthreads bugcheck.
A common example is the use of any mutex operation or of certain condition variable operations from within an interrupt routine (that is, a DIGITAL UNIX signal handler or OpenVMS AST routine). This type of programming error most commonly results in a bugcheck that reports an "krnSpinLockPrm: deadlock detected" message or a "Can't find null thread" message. To prevent this type of error, avoid using DECthreads routines other than pthread_cond_signal_int_np() from an interrupt routine (or the equivalent routines in other APIs).
In addition, DECthreads maintains a variety of state information in memory which can be overwritten by your own code. Therefore, it is possible for an application to accidentally modify DECthreads state by writing through invalid pointers, which can result in a bugcheck or other undesirable behavior.
A thread-safe library typically consists of routines that do not themselves create or use threads. However, the routines in a thread-safe library must be coded so that they are safe to be called from applications that use threads. DECthreads provides the thread-independent services (or tis) interface to support writing 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 very efficient. 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 DECthreads synchronization, such as synchronization objects and thread joining.
The guidelines for using the DECthreads 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 DECthreads tis interface are:
Implementation of the DECthreads tis interface library varies by Compaq operating system. For more information, see this guide's operating system-specific appendixes.
It is not difficult to create thread-safe code using the DECthreads
tis interface, and with the source code available
should be staightforward to convert existing code that is not thread
safe to become 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 DECthreads. See Section 3.9.4.
Although the C, C++, Pascal, and BLISS programming languages do not
support multithreaded programming directly, compilers for those
languages generally create reentrant code. However, the Fortran and
COBOL languages are defined in such a way that compilers can make
implicit use of static storage, and such compilers do not generate
reentrant code; it is difficult to write reentrant code in a
nonreentrant language. The DEC FORTRAN compiler does generate reentrant
code.
4.1.2 Performance of tis Interface Routines
Routines in the DECthreads tis interface are designed
to perform efficiently when called from a single-threaded environment.
For example, locking a mutex is essentially just setting a bit, and
unlocking the mutex requires clearing 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, even when invoked from a multithreaded environment. For a multithreaded program that uses tis routines, during program initialization DECthreads automatically revectors the program's run-time linkages to most tis routines. This allows subsequent calls to those routines to use the normal DECthreads 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 DECthreads operation is transparent to library code that uses the tis interface. For instance, if DECthreads is dynamically activated while a tis mutex is acquired, the mutex can be released normally.
The tis interface deliberately provides no way to
determine whether DECthreads is active within the process. Thread-safe
code should always act as if multiple threads can be active. To do
otherwise inevitably results in incorrect program behavior, given that
DECthreads can be dynamically activated into the process at any time.
4.1.4 Cancelation Points
The following routines in the DECthreads tis interface are cancelation points:
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
The tis interface routines support mutexes, called tis mutexes. Like the kinds of mutexes available through the other DECthreads interfaces, tis mutexes provide synchronization between multiple threads that share resources. In fact, you can statically initialize tis mutexes using the POSIX 1003.1c standard PTHREAD_MUTEX_INITIALIZER macro (see the DECthreads pthread.h header file) or using one of the nonportable DECthreads variants. This means you can create DECthreads recursive or errorcheck mutexes if required, as well as normal mutexes.
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.
If the DECthreads multithreading run-time environment becomes initialized dynamically, any tis mutexes acquired by your program remain acquired. The ownership of recursive and errorcheck mutexes remains valid.
Operations on the DECthreads global lock are also supported by
tis interface routines. The DECthreads global lock is
a recursive mutex that is reserved by DECthreads but is for use by any
thread. Your program can use the global lock without calling the other
DECthreads interfaces 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 POSIX.1c standard PTHREAD_COND_INITIALIZER macro or using one of the various nonportable DECthreads variants. For example, you can assign names to your tis condition variables by statically initializing them with the PTHREAD_COND_INITWITHNAME_NP 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.
Of course, your program can have more than one thread only if the DECthreads multithreading run-time environment is present. That is, if your program were to wait, there would be no other thread to "awaken" your program. Signaling or broadcasting a tis mutex when called from a single-threaded environment does nothing. This is because your program is not allowed to wait on a tis condition variable when the DECthreads multithreading run-time environment has not been initialized.
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.
4.4 Using Thread-Specific Data
The tis interface routines support the use of thread-specific data variables. If the DECthreads multithreading run-time environment is initialized, tis thread-specific data keys are transferred into that environment, so your program can continue to use the same keys.
For a program that uses tis thread-specific data in a multithreaded
environment, any thread-specific data values set using a routine in the
tis interface will be transferred into your program's
initial thread.
4.5 Using Read-Write Locks
A read-write lock is an object that allows the application to serialize access to information that can be accessed 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 and can be called by either a routine in a thread or by a routine in a thread-safe, single-threaded program.
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.
Only the tis interface offers routines that operate on read-write locks. This is not a problem because you can use tis routines in a program that uses another DECthreads interface to use its own threads.
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 or program 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, DECthreads gives 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. 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 allocating and initializing a 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() immediately returns [EBUSY], rather than waiting.
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.
Use the following routines to manipulate read-write locks:
Routine | Description |
---|---|
tis_rwlock_init() | Initializes a read-write lock. |
tis_rwlock_destroy() | Destroys a read-write lock. |
tis_read_lock() | Acquires a read-write lock for read access. |
tis_write_lock() | Acquires a read-write lock for write access. |
tis_read_trylock() | Attempts to acquire a read-write lock for read access without waiting. |
tis_write_trylock() | Attempts to acquire a read-write lock for write access without waiting. |
tis_read_unlock() | Unlocks a read-write lock acquired for read access. |
tis_write_unlock() | Unlocks a read-write lock acquired for write access. |
For more information about each tis interface routine that manipulates a read-write lock, see Part 3.
Previous | Next | Contents | Index |
Copyright © Compaq Computer Corporation 1998. All rights reserved. Legal |
6101PRO_008.HTML
|