[Digital logo]
[HR]

Guide to DECthreads


Previous | Contents

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.8.4 Use of Multiple Threads Libraries Not Supported

DECthreads performs user mode execution context-switching within a process or virtual processor 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 OpenVMS Alpha, because it uses DECthreads.)

3.9 Reporting 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 creates and 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. Use the PTHREAD_CONFIG dump option to cause DECthreads to write the bugcheck information into a different file.

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.

3.9.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, and the (usually) final line 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 you can get from the pthread_debug() interface. This information is usually necessary to track down the problem. If you encounter a DECthreads bugcheck, please contact your Digital support representative and include this information file along with sample code and output.

3.9.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 "enter_kernel: deadlock" message or a "Can't find null thread" message. To prevent this type of error, avoid using condition variables operations other than pthread_cond_signal_int_np() from an interrupt routine (or from 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 (that is, synchronization objects and joining with threads). 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 Digital operation 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.8.4.

Although the C, Pascal, and BLISS 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.

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.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 defined in the DECthreads C language header file pthread.h, or using one of the various nonportable DECthreads variants. That means you can create recursive or errorcheck mutexes if you need them, as well as normal mutexes. You can also assign names to your program's tis mutexes.

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. That means it is legal and reasonable for your program to lock a tis mutex, dynamically activate the DECthreads library, and then unlock the mutex.

Operations on the DECthreads global lock (a recursive mutex reserved by DECthreads and for use by any thread) are also supported by tis interface routines. 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 DECthreads condition variables. You can initialize them statically using the POSIX 1003.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 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.

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.

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.

In a single-threaded environment, do not use condition variables (for example, with tis_cond_wait() to block your program's processing.

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 Readers/Writer Locks

A readers/writer lock is an object that serializes access to shared information that needs to be read frequently and written only occasionally. Routines that manipulate readers/writer locks 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 readers/writer locks. There are no equivalent objects available within the DECthreads pthread or cma interfaces. This is not a problem because you can use tis routines in a program that uses another DECthreads interface to use its own threads.


Note

In this version of DECthreads readers/writer locks are not portable---although the IEEE POSIX 1003.1j standard (in balloting when this document went to press) proposes a very similar set of functions that will eventually be made available in DECthreads.

Your program can acquire a readers/writer lock with shared read access or for exclusive write access. An attempt to acquire a readers/writer lock with read access will block when any thread or program has already acquired that lock with write access. An attempt to acquire a readers/writer lock with write access will block when another thread has already acquired that lock with either write access or read access.

In a multithreaded environment, when both readers and writers are waiting at the same time for access via an already acquired readers/writer 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 readers/writer lock's behavior in response to three threads (one writer and two readers) that must access the same memory object.

Figure 4-1 Readers/Writer Lock Behavior



The tis_rwlock_init() routine initializes a readers/writer 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 readers/writer lock when access to a shared resource is required. tis_read_trylock() and tis_write_trylock() can also be called to acquire a readers/writer 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 readers/writer lock. This routine frees the lock's resources for re-use.

Use the following routines to manipulate readers/writer locks:
Routine Description
tis_rwlock_init() Initializes a readers/writer lock.
tis_rwlock_destroy() Destroys a readers/writer lock.
tis_read_lock() Acquires a read lock.
tis_write_lock() Acquires a write lock.
tis_read_trylock() Attempts to acquire a read lock without waiting.
tis_write_trylock() Attempts to acquire a write lock without waiting.
tis_read_unlock() Unlocks the read lock.
tis_write_unlock() Unlocks the write lock.

For more information about each tis interface routine that manipulates a readers/writer lock, see Part 3.


Chapter 5
Using the DECthreads Exception Package

This chapter introduces conventions for the modular use of exceptions in a multithreaded program.

Although the DECthreads pthread interface reports errors by returning nonzero failure codes, DECthreads uses exceptions in the following cases:

The DECthreads exception package is most useful when you are programming in the C language or with other cross-platform routines that use the pthread interface.


Note

On OpenVMS systems, you can use the OpenVMS Condition Handling Facility to catch DECthreads exceptions.

On Digital UNIX systems, you can use the C language exception library to catch DECthreads exceptions. You cannot use the C++ language exception library to catch DECthreads exceptions.


5.1 Overview of Exceptions

An exception is an object that describes an error condition. Operations on exception objects allow errors to be reported and handled. If an exception is handled properly, the program can recover from errors. For example, if an exception is raised from a parity error while reading a tape, the recovery action might be to retry 100 times before giving up.

Using a few simple macros, your program's C functions can declare a block of code, called an exception scope, where exceptions are to be caught. Within an exception scope you can define a block of code to respond to a specific exception or to all exceptions. DECthreads exception handlers are attached, which means that the handler code appears within the block where exceptions are caught. This allows the programmer to see what actions are taken when an exception occurs.

There are two ways for your program to respond to an exception that occurs within an exception scope:

5.1.1 Types of Exceptions

There are two types of exceptions:

A DECthreads exception is initialized as an address exception, but it can be modified before it is used by defining a status value for it.

Following are the primary differences between address and status exceptions:

Status values used in exceptions can be interpreted, handled, and reported in a universal manner, regardless of which facility defined the status value. Use address exceptions if your code does not have a range of status codes assigned to it. Address exceptions are always unique so you do not risk colliding with another facility's status codes and inadvertently handling the wrong exception. Also, address exceptions are more portable because status codes are likely to be different on each platform.

5.1.2 Terminating Exception Semantics

DECthreads exceptions are terminating exceptions. This means that control never returns to the instruction following a RAISE statement. When an exception occurs because of a hardware condition, such as an illegal address, execution cannot be resumed at the failing instruction. An exception causes execution of handlers that your program has declared (starting with the most recently declared handler and proceeding backwards) until a CATCH or CATCH_ALL clause is reached that does not end with RERAISE. At this point, execution continues at the first statement after the ENDTRY that terminates that current handler.


Note

On OpenVMS systems:

All exceptions are of SEVERE (FATAL) severity. When you set the status of an exception using pthread_exc_set_status_np(), DECthreads sets the severity field to SEVERE (4). Also, DECthreads raises exceptions (RAISE or pthread_exc_raise_status_np()) using LIB$STOP, which also sets the severity to SEVERE.

The CATCH, CATCH_ALL, and FINALLY macros cannot be used to handle any status exception that is not a SEVERE severity level.


5.2 Exception Operations

The DECthreads exception package supports the following operations on exceptions:

These operations are discussed in the following sections.

5.2.1 Declaring and Initializing an Exception Object

An exception object is an opaque type that should only be manipulated by the DECthreads exception package functions. The actual definition of the type may differ from one DECthreads release to another.

Declaring and initializing an exception object documents that a program reports or handles a particular error. Having the error expressed as an exception object provides future extensibility as well as portability.

An exception is declared as a variable of type EXCEPTION. In general, you should declare the type as static or extern. For example:

 
   static EXCEPTION an_error; 
 

Because on some platforms an exception object may require dynamic initialization, the DECthreads exception package requires a run-time initialization call in addition to the declaration. The initialization function is a macro named EXCEPTION_INIT. The name of the exception is passed as a parameter.

Following is an example of declaring and initializing an exception object:

 
   EXCEPTION parity_error;         /* Declare it */ 
   EXCEPTION_INIT (parity_error);  /* Initialize it */ 
 

5.2.2 Raising an Exception

Raising an exception reports an error not by returning a value, but by propagating the exception. Propagation involves searching all active scopes for code written to handle the error or code written to perform finalization actions in case of any error, and then causing that code to execute. If a scope does not define a handler or finalization block, then the scope is simply torn down as the exception propagates up the stack. This is sometimes referred to as unwinding the stack.

Because DECthreads exceptions are terminating, there is no option to make execution resume at the point of the error. Execution resumes at the point where the exception is caught.

If an exception is unhandled, the process is terminated. (On OpenVMS systems, DECthreads exceptions are raised using LIB$STOP, which always sets the condition code to a level of SEVERE (4).) Termination prevents the unhandled error from affecting other areas of the program.

An example of raising an exception follows:

 
   RAISE (parity_error); 
 

5.2.3 Defining a Code Region to Catch Exceptions

The TRY macro defines the beginning of an exception scope, and the ENDTRY macro defines the end of the scope. These macros allow the programmer to define a scope (a block) wherein exceptions can be caught. Any exceptions raised within the block, or within any functions that are called directly or indirectly by the block, pass through the control of this scope. If it is desirable to continue propagation, these exceptions can be caught and explicitly reraised, or they can be ignored, which implicitly reraises them.


Previous | Next | Contents | [Home] | [Comments] | [Ordering info] | [Help]

[HR]

  6493P005.HTM
  OSSG Documentation
  22-NOV-1996 13:20:02.50

Copyright © Digital Equipment Corporation 1996. All Rights Reserved.

Legal