Guide to DECthreads
3.9.3 Lacking Thread Safety
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.
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.
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.
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.
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.
DECthreads can detect some of the following types of errors:
-
Application programming interface (API) errors can occur when the
program specifies an invalid parameter or attempts an inappropriate
operation on some DECthreads object.
-
Internal errors can occur when DECthreads determines that internal
information has become corrupted to the point where it cannot continue
operation.
API errors are reported in different ways by the various DECthreads
interfaces:
- The DECthreads pthread interface returns an
integer value indicating the type of error.
- The DECthreads cma interface raises exceptions to
indicate error conditions.
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.
Note
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.
|
3.10.1 Contents of a DECthreads Bugcheck Dump File
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.
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.
Chapter 4 Writing Thread-Safe Libraries
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.
Among the key features of the DECthreads tis interface
are:
- Distinct tis library, with some unique routines
and some routines that correspond to those in the DECthreads
pthread interface
- Common synchronization objects (such as mutexes and condition
variables) with the pthread interface
- Unique tis synchronization objects (such as the
read-write lock)
- Support for thread-specific data objects
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.
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.
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.
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.
The following routines in the DECthreads 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.
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().
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.
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.
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.
|