Chapter 10. Future standardization

Three primary standardization efforts affect Pthreads programmers. X/Open’s XSH5 is a new interface specification that includes POSIX.1b, Pthreads, and a set of additional thread functions (part of the Aspen fast-track submission). The POSIX.1j draft standard proposes to add barriers, read/write locks, spinlocks, and improved support for “relative time” waits on condition variables. The POSIX.14 draft standard (a “POSIX Standard Profile”) gives direction for managing the various options of Pthreads in a multiprocessor environment.

X/Open XSH5 [UNIX98]

Mutex type attribute:

int pthread_mutexattr_gettype (                          
    const pthread_mutexattr_t *attr, int *type);         
int pthread_mutexattr_settype (                          
    pthread_mutexattr_t *attr, int type);                

Read/write locks:

int pthread_rwlock_init (pthread_rwlock_t *rwlock,       
    const pthread_rwlockattr_t *attr);                   
int pthread_rwlock_destroy (pthread_rwlock_t *rwlock);   
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;    
int pthread_rwlock_rdlock (pthread_rwlock_t *rwlock);    
int pthread_rwlock_tryrdlock (                           
    pthread_rwlock_t *rwlock);                           
int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);    
int pthread_rwlock_wrlock (pthread_rwlock_t *rwlock);    
int pthread_rwlock_trywrlock (                           
    pthread_rwlock_t *rwlock);                           
int pthread_rwlockattr_init (                            
    pthread_rwlockattr_t *attr);                         
int pthread_rwlockattr_destroy (                         
    pthread_rwlockattr_t *attr);                         
int pthread_rwlockattr_getpshared (                      
    const pthread_rwlockattr_t *attr, int *pshared);     
int pthread_rwlockattr_setpshared (                      
    pthread_rwlockattr_t *attr, int pshared);            

Parallel I/O:

size_t pread (int fildes,                                
    void *buf, size_t nbyte, off_t offset);              
size_t pwrite (int fildes,                               
    const void *buf, size_t nbyte, off_t offset);        

Miscellaneous:

int pthread_attr_getguardsize (                          
    const pthread_attr_t *attr, size_t *guardsize);      
int pthread_attr_setguardsize (                          
    pthread_attr_t *attr, size_t guardsize);             
int pthread_getconcurrency (void);                       
int pthread_setconcurrency (int new_level);              

X/Open, which is part of The Open Group, owns the UNIX trademark and develops UNIX industry portability specifications and brands. The X/Open brands include XPG3, XPG4, UNIX93, and UNIX95. UNIX95 is also known as “SPEC1170” or the “Single UNIX Specification.”

X/Open recently published the X/Open CAE Specification, System Interfaces and Headers, Issue 5 (also known as XSH5), which is part of the new UNIX98 brand. XSH5 requires conformance to the POSIX.1–1996 standard, which includes the POSIX.1b and POSIX.1c amendments. The XSH5 specification also adds a set of extensions to POSIX. This section discusses the XSH5 extensions that specifically affect threaded programs. You can recognize a system conforming to XSH5 by a definition for the _XOPEN_VERSION symbol, in <unistd.h>, to the value 500 or higher.

The most valuable contribution of UNIX98 to the threaded programming industry, however, is possibly the development of a standardized, portable testing system. A number of complicated issues arise when developing an implementation of Pthreads, and some subtle aspects of the standard are ambiguous. Such an industry-wide testing system will require all vendors implementing UNIX98 branded systems to agree on interpretations of Pthreads.

POSIX options for XSH5

Some of the features that are options in the Pthreads standard are required by XSH5. If your code relies on these Pthreads options, it will work on any system conforming to XSH5:

  • _POSIX_THREADS: Threads are supported.

  • _POSIX_THREAD_ATTR_STACKADDR: Thestackaddr attribute is supported.

  • _POSIX_THREAD_ATTR_STACKSIZE: The stacksize attribute is supported.

  • _POSIX_THREAD_PROCESS_SHARED: Mutexes, condition variables, and XSH5 read/write locks can be shared between processes.

  • _POSIX_THREAD_SAFE_FUNCTIONS: The Pthreads thread-safe functions are supported.

Several additional Pthreads options are “bundled” into the XSH5 realtime threads option group. If your system conforms to XSH5 and supports the _XOPEN_REALTIME_THREADS option, then these Pthreads options are also supported:

  • _POSIX_THREAD_PRIORITY_SCHEDULING: Realtime priority scheduling is supported.

  • _POSIX_THREAD_PRIO_PROTECT: Priority ceiling mutexes are supported.

  • _POSIX_THREAD_PRIO_INHERIT: Priority inheritance mutexes are supported.

Mutex type

The DCE threads package provided an extension that allowed the programmer to specify the “kind” of mutex to be created. DCE threads supplied fast, recursive, and nonrecursive mutex kinds. The XSH5 specification changes the attribute name from “kind” to “type,” renames fast to default, renames nonrecursive to errorcheck, and adds a new type, normal (Table 10.1).

Table 10.1. XSH5 mutex types

Mutex type

Definition

PTHREAD_MUTEX_NORMAL

Basic mutex with no specific error checking built in. Does not report a deadlock error.

PTHREAD_MUTEX_RECURSIVE

Allows any thread to lock the mutex “recursively”—it must unlock an equal number of times to release the mutex.

PTHREAD_MUTEX_ERRORCHECK

Detects and reports simple usage errors—an attempt to unlock a mutex that’s not locked by the calling thread (or that isn’t locked at all), or an attempt to relock a mutex the thread already owns.

PTHREAD_MUTEX_DEFAULT

The default mutex type, with very loose semantics to allow unfettered innovation and experimentation. May be mapped to any of the other three defined types, or may be something else entirely.

A normal mutex is not allowed to detect deadlock errors—that is, a thread will hang if it tries to lock a normal mutex that it already owns. The default mutex type, like the DCE fast mutex,[*] provides implementation-defined error checking. That is, default may be mapped to one of the other standard types or may be something entirely different.

As an application developer, you can use any of the mutex types almost interchangeably as long as your code does not depend on the implementation to detect (or fail to detect) any particular errors. Never write code that counts on an implementation failing to detect any error. Do not lock a mutex in one thread and unlock it in another thread, for example, even if you are sure that the error won’t be reported—use a semaphore instead, which has no “ownership” semantics.

All mutexes, regardless of type, are created using pthread_mutex_init, destroyed using pthread_mutex_destroy, and manipulated using pthread_mutex_lock, pthread_mutex_unlock, and pthread_mutex_trylock.

Normal mutexes will usually be the fastest implementation possible for the machine, but will provide the least error checking.

Recursive mutexes are primarily useful for converting old code where it is difficult to establish clear boundaries of synchronization, for example, when you must call a function with a mutex locked and the function you call—or some function it calls—may need to lock the same mutex. I have never seen a situation where recursive mutexes were required to solve a problem, but I have seen many cases where the alternate (and usually “better”) solutions were impractical. Such situations frequently lead developers to create recursive mutexes, and it makes more sense to have a single implementation available to everyone. (But your code will usually be easier to follow, and perform better, if you avoid recursive mutexes.)

Errorcheck mutexes were devised as a debugging tool, although less intrusive debugging tools (where available) can be more powerful. To use errorcheck mutexes you must recompile code to turn the debugging feature on and off. It is far more useful to have an external option to force all mutexes to record debugging data. You may want to use errorcheck mutexes in final “production” code, of course, to detect serious problems early, but be aware that errorcheck mutexes will almost always be much slower than normal mutexes due to the extra state and checking.

Default mutexes allow each implementation to provide the mutex semantics the vendor feels will be most useful to the target audience. It may be useful to make errorcheck mutexes the default, for example, to improve the threaded debugging environment of a system. Or the vendor may choose to make normal mutexes the default to give most programs the benefit of any extra speed.

Example . 

pthread_mutexattr_gettype

int pthread_mutexattr_gettype (
        const pthread_mutexattr_t      *attr,
        int                            *type);

Determine the type of mutexes created with attr.

type

PTHREAD_MUTEX_DEFAULT

Unspecified type.

PTHREAD_MUTEX_NORMAL

Basic mutex, with no error checking.

PTHREAD_MUTEX_RECURSIVE

Thread can relock a mutex it owns.

PTHREAD_MUTEX_ERRORCHECK

Checks for usage errors.

References:

3.2, 5.2.1, 10.1.2

Errors:

[EINVAL] type invalid. [EINVAL] attr invalid.

Hint:

Normal mutexes will usually be fastest; errorcheck mutexes are useful for debugging; recursive mutexes can be useful for making old interfaces thread-safe.

Example . 

pthread_mutexattr_settype

int pthread_mutexattr_settype (
        pthread_mutexattr_t              *attr,
        int                              type);

Specify the type of mutexes created with attr.

type

PTHREAD_MUTEX_DEFAULT

Unspecified type.

PTHREAD_MUTEX_NORMAL

Basic mutex, with no error checking.

PTHREAD_MUTEX_RECURSIVE

Thread can relock a mutex it owns.

PTHREAD_MUTEX_ERRORCHECK

Checks for usage errors.

References:

3.2, 5.2.1, 10.1.2

Errors:

[EINVAL] type invalid. [EINVAL] attr invalid.

Hint:

Normal mutexes will usually be fastest; errorcheck mutexes are useful for debugging; recursive mutexes can be useful for making old interfaces thread-safe.

Set concurrency level

When you use Pthreads implementations that schedule user threads onto some smaller set of kernel entities (see Section 5.6.3), it may be possible to have ready user threads while all kernel entities allocated to the process are busy. Some implementations, for example, “lock” a kernel entity to a user thread that blocks in the kernel, until the blocking condition, for example an I/O request, is completed. The system will create some reasonable number of kernel execution entities for the process, but eventually the pool of kernel entities may become exhausted. The process may be left with threads capable of performing useful work for the application, but no way to schedule them.

The pthread_setconcurrency function addresses this limitation by allowing the application to ask for more kernel entities. If the application designer realizes that 10 out of 15 threads may at any time become blocked in the kernel, and it is important for those other 5 threads to be able to continue processing, then the application may request that the kernel supply 15 kernel entities. If it is important that at least 1 of those 5 continue, but not that all continue, then the application could request the more conservative number of 11 kernel entities. Or if it is OK for all threads to block once in a while, but not often, and you know that only rarely will more than 6 threads block at any time, the application could request 7 kernel entities.

The pthread_setconcurrency function is a hint, and implementations may ignore it or modify the advice. You may use it freely on any system that conforms to the UNIX98 brand, but many systems will do nothing more than set a value that is returned by pthread_getconcurrency. On Digital UNIX, for example, there is no need to set a fixed concurrency level, because the kernel mode and user mode schedulers cooperate to ensure that ready user threads cannot be prevented from running by other threads blocked in the kernel.

Example . 

pthread_getconcurrency

int pthread_getconcurrency (void);

Returns the value set by a previous pthread_setconcurrency call. If there have been no previous calls to pthread_setconcurrency, returns 0 to indicate that the implementation is maintaining the concurrency level automatically.

References:

5.6.3, 10.1.3

Errors:

none.

Hint:

Concurrency level is a hint. It may be ignored by any implementation, and will be ignored by an implementation that does not need it to ensure concurrency.

Example . 

pthread_setconcurrency

int pthread_setconcurrency (int new_level);

Allows the application to inform the threads implementation of its desired minimum concurrency level. The actual level of concurrency resulting from this call is unspecified.

References:

5.6.3, 10.1.3

Errors:

[EINVAL] new_level is negative. [EAGAIN] new_level exceeds a system resource.

Hint:

Concurrency level is a hint. It may be ignored by any implementation, and will be ignored by an implementation that does not need it to ensure concurrency.

Stack guard size

Guard size comes from DCE threads. Most thread implementations add to the thread’s stack a “guard” region, a page or more of protected memory. This protected page is a safety zone, to prevent a stack overflow in one thread from corrupting another thread’s stack. There are two good reasons for wanting to control a thread’s guard size:

  1. It allows an application or library that allocates large data arrays on the stack to increase the default guard size. For example, if a thread allocates two pages at once, a single guard page provides little protection against stack overflows—the thread can corrupt adjoining memory without touching the protected page.

  2. When creating a large number of threads, it may be that the extra page for each stack can become a severe burden. In addition to the extra page, the kernel’s memory manager has to keep track of the differing protection on adjoining pages, which may strain system resources. Therefore, you may sometimes need to ask the system to “trust you” and avoid allocating any guard pages at all for your threads. You can do this by requesting a guard size of 0 bytes.

Example . 

pthread_attr_getguardsize

int pthread_attr_getguardsize (
        const pthread_attr_t     *attr,
        size_t                   *guardsize);

Determine the size of the guard region for the stack on which threads created with attr will run.

References:

2, 5.2.3

Errors:

[EINVAL] attr invalid.

Hint:

Specify 0 to fit lots of stacks in an address space, or increase default guardsize for threads that allocate large buffers on the stack.

Example . 

pthread_attr_setguardsize

int pthread_attr_setguardsize (
        pthread_attr_t           *attr,
        size_t                   guardsize);

Threads created with attr will run on a stack with guardsize bytes protected against stack overflow. The implementation may round guardsize up to the next multiple of PAGESIZE. Specifying a value of 0 for guardsize will cause threads created using the attributes object to run without stack overflow protection.

References:

2, 5.2.3

Errors:

[EINVAL] guardsize or attr invalid.

Hint:

Specify 0 to fit lots of stacks in an address space, or increase default guardsize for threads that allocate large buffers on the stack.

Parallel I/O

Many high-performance systems, such as database engines, use threads, at least in part, to gain performance through parallel I/O. Unfortunately, Pthreads doesn’t directly support parallel I/O. That is, two threads can independently issue I/O operations for files, or even for the same file, but the POSIX file I/O model places some restrictions on the level of parallelism.

One bottleneck is that the current file position is an attribute of the file descriptor. To read or write data from or to a specific position within a file, a thread must call lseek to seek to the proper byte offset in the file, and then read or write. If more than one thread does this at the same time, the first thread might seek, and then the second thread seek to a different place before the first thread can issue the read or write operation.

The X/Open pread and pwrite functions offer a solution, by making the seek and read or write combination atomic. Threads can issue pread or pwrite operations in parallel, and, in principle, the system can process those I/O requests completely in parallel without locking the file descriptor.

Example . 

pread

size_t pread (
        int                  fildes,
        void                 *buf,
        size_t               nbyte,
        off_t                offset);

Read nbyte bytes from offset offset in the file opened on file descriptor fildes, placing the result into buf. The file descriptor’s current offset is not affected, allowing multiple pread and/or pwrite operations to proceed in parallel.

References:

none

Errors:

[EINVAL] offset is negative. [EOVERFLOW] attempt to read beyond maximum. [ENXIO] request outside capabilities of device. [ESPIPE] file is pipe.

Hint:

Allows high-performance parallel I/O.

Example . 

pwrite

size_t pwrite (
        int                  fildes,
        const void           *buf,
        size_t               nbyte,
        off_t                offset);

Write nbyte bytes to offset offset in the file opened on file descriptor fildes, from buf. The file descriptor’s current offset is not affected, allowing multiple pread and/or pwrite operations to proceed in parallel.

References:

none

Errors:

[EINVAL] offset is negative. [ESPIPE] file is pipe.

Hint:

Allows high-performance parallel I/O.

Cancellation points

Most UNIX systems support a substantial number of interfaces that do not come from POSIX. The select and poll interfaces, for example, should be deferred cancellation points. Pthreads did not require these functions to be cancellation points, however, because they do not exist within POSIX.1.

The select and poll functions, however, along with many others, exist in X/Open. The XSH5 standard includes an expanded list of cancellation points covering X/Open interfaces.

Additional functions that must be cancellation points in XSH5:

getmsg          pread          sigpause
getpmsg         putmsg         usleep
lockf           putpmsg        wait3
msgrcv          pwrite         waitid
msgsnd          readv          writev
poll            select

Additional functions that may be cancellation points in XSH5:

catclose      fsetpos      popen
catgets       ftello       pututxline
catopen       ftw          putw
closelog      fwprintf     putwc
dbm_close     fwscanf      putwchar
dbm_delete    getgrent     readdir_r
dbm_fetch     getpwent     seekdir
dbm_nextkey   getutxent    semop
dbm_open      getutxid     setgrent
dbm_store     getutxline   setpwent
dlclose       getw         setutxent
dlopen        getwc        syslog
endgrent      getwchar     ungetwc
endpwent      iconv_close  vfprintf
endutxent     iconv_open   vfwprintf
fgetwc        ioctl        vprintf
fgetws        mkstemp      vwprintf
fputwc        nftw         wprintf
fputws        openlog      wscanf
fseeko        pclose

POSIX 1003.1j

Condition variable wait clock:

int pthread_condattr_getclock (        
    const pthread_condattr_t *attr,    
    clockid_t *clock_id);              
int pthread_condattr_setclock (        
    pthread_condattr_t *attr,          
    clockid_t clock_id);               

Barriers:

int barrier_attr_init (barrier_attr_t *attr);         
int barrier_attr_destroy (barrier_attr_t *attr);      
int barrier_attr_getpshared (                         
    const barrier_attr_t *attr, int *pshared);        
int barrier_attr_setpshared (                         
    barrier_attr_t *attr, int pshared);               
int barrier_init (barrier_t *barrier,                 
    const barrier_attr_t *attr, int count);           
int barrier_destroy (barrier_t *barrier);             
int barrier_wait (barrier_t *barrier);                

Reader/writer locks:

int rwlock_attr_init (rwlock_attr_t *attr);           
int rwlock_attr_destroy (rwlock_attr_t *attr);        
int rwlock_attr_getpshared (                          
    const rwlock_attr_t *attr, int *pshared);         
int rwlock_attr_setpshared (                          
    rwlock_attr_t *attr, int pshared);                
int rwlock_init (                                     
    rwlock_t *lock, const rwlock_attr_t *attr);       
int rwlock_destroy (rwlock_t *lock);                  
int rwlock_rlock (rwlock_t *lock);                    
int rwlock_timedrlock (rwlock_t *lock,                
    const struct timespec *timeout);                  
int rwlock_tryrlock (rwlock_t *lock);                 
int rwlock_wlock (rwlock_t *lock);                    
int rwlock_timedwlock (rwlock_t *lock,                
    const struct timespec *timeout);                  
int rwlock_trywlock (rwlock_t *lock);                 
int rwlock_unlock (rwlock_t *lock);                   

Spinlocks:

int spin_init (spinlock_t *lock);                     
int spin_destroy (spinlock_t *lock);                  
int spin_lock (spinlock_t *lock);                     
int spin_trylock (spinlock_t *lock);                  
int spin_unlock (spinlock_t *lock);                   
int pthread_spin_init (pthread_spinlock_t *lock);     
int pthread_spin_destroy (pthread_spinlock_t *lock);  
int pthread_spin_lock (pthread_spinlock_t *lock);     
int pthread_spin_trylock (pthread_spinlock_t *lock);  
int pthread_spin_unlock (pthread_spinlock_t *lock);   

Thread abort:

int pthread_abort (pthread_t thread);                 

The same POSIX working group that developed POSIX.1b and Pthreads has developed a new set of extensions for realtime and threaded programming. Most of the extensions relevant to threads (and to this book) are the result of proposals developed by the POSIX 1003.14 profile group, which specialized in “tuning” the existing POSIX standards for multiprocessor systems.

POSIX.1j adds some thread synchronization mechanisms that have been common in a wide range of multiprocessor and thread programming, but that had been omitted from the original Pthreads standard. Barriers and spinlocks are primarily useful for fine-grained parallelism, for example, in systems that automatically generate parallel code from program loops. Read/write locks are useful in shared data algorithms where many threads are allowed to read simultaneously, but only one thread can be allowed to update data.

Barriers

“Barriers” are a form of synchronization most commonly used in parallel decomposition of loops. They’re almost never used except in code designed to run only on multiprocessor systems. A barrier is a “meeting place” for a group of associated threads, where each will wait until all have reached the barrier. When the last one waits on the barrier, all the participating threads are released.

See Section 7.1.1 for details of barrier behavior and for an example showing how to implement a barrier using standard Pthreads synchronization. (Note that the behavior of this example is not precisely the same as that proposed by POSIX.1j.)

Read/write locks

A read/write lock (also sometimes known as “reader/writer lock”) allows one thread to exclusively lock some shared data to write or modify that data, but also allows multiple threads to simultaneously lock the data for read access. UNIX98 specifies “read/write locks” very similar to POSIX.1j reader/writer locks. Although X/Open intends that the two specifications will be functionally identical, the names are different to avoid conflict should the POSIX standard change before approval.[*]

If your code relies on a data structure that is frequently referenced, but only occasionally updated, you should consider using a read/write lock rather than a mutex to access that data. Most threads will be able to read the data without waiting; they’ll need to block only when some thread is in the process of modifying the data. (Similarly, a thread that desires to write the data will be blocked if any threads are reading the data.)

See Section 7.1.2 for details of read/write lock behavior and for an example showing how to implement a read/write lock using standard Pthreads synchronization. (Note that the behavior of this example is not precisely the same as that proposed by POSIX.1j.)

Spinlocks

Spinlocks are much like mutexes. There’s been a lot of discussion about whether it even makes sense to standardize on a spinlock interface—since POSIX specifies only a source level API, there’s very little POSIX.1j says about them that distinguishes them from mutexes. The essential idea is that a spinlock is the most primitive and fastest synchronization mechanism available on a given hardware architecture. On some systems, that may be a single “test and set” instruction—on others, it may be a substantial sequence of “load locked, test, store conditional, memory barrier” instructions.

The critical distinction is that a thread trying to lock a spinlock does not necessarily block when the spinlock is already held by another thread. The intent is that the thread will “spin,” retrying the lock rapidly until it succeeds in locking the spinlock. (This is one of the “iffy” spots—on a uniprocessor it had better block, or it’ll do nothing but spin out the rest of its timeslice . . . or spin to eternity if it isn’t timesliced.)

Spinlocks are great for fine-grained parallelism, when the code is intended to run only on a multiprocessor, carefully tuned to hold the spinlock for only a few instructions, and getting ultimate performance is more important than sharing the system resources cordially with other processes. To be effective, a spinlock must never be locked for as long as it takes to “context switch” from one thread to another. If it does take as long or longer, you’ll get better overall performance by blocking and allowing some other thread to do useful work.

POSIX.1j contains two sets of spinlock functions: one set with a spin_ prefix, which allows spinlock synchronization between processes; and the other set with a pthread_ prefix, allowing spinlock synchronization between threads within a process. This, you will notice, is very different from the model used for mutexes, condition variables, and read/write locks, where the same functions were used and the pshared attribute specifies whether the resulting synchronization object can be shared between processes.

The rationale for this is that spinlocks are intended to be very fast, and should not be subject to any possible overhead as a result of needing to decide, at run time, how to behave. It is, in fact, unlikely that the implementation of spin_lock and pthread_spin_lock will differ on most systems, but the standard allows them to be different.

Condition variable wait clock

Pthreads condition variables support only “absolute time” timeouts. That is, the thread specifies that it is willing to wait until “Jan 1 00:00:00 GMT 2001,” rather than being able to specify that it wants to wait for “1 hour, 10 minutes.” The reason for this is that a condition variable wait is subject to wakeups for various reasons that are beyond your control or not easy to control. When you wake early from a “1 hour, 10 minute” wait it is difficult to determine how much of that time is left. But when you wake early from the absolute wait, your target time is still “Jan 1 00:00:00 GMT 2001.” (The reasons for early wakeup are discussed in Section 3.3.2.)

Despite all this excellent reasoning, “relative time” waits are useful. One important advantage is that absolute system time is subject to external changes. It might be modified to correct for an inaccurate clock chip, or brought up-to-date with a network time server, or adjusted for any number of other reasons. Both relative time waits and absolute time waits remain correct across that adjustment, but a relative time wait expressed as if it were an absolute time wait cannot. That is, when you want to wait for “1 hour, 10 minutes,” but the best you can do is add that interval to the current clock and wait until that clock time, the system can’t adjust the absolute timeout for you when the system time is changed.

POSIX.1j addresses this issue as part of a substantial and pervasive “cleanup” of POSIX time services. The standard (building on top of POSIX.1b, which introduced the realtime clock functions, and the CLOCK_REALTIME clock) introduces a new system clock called CLOCK_MONOTONIC. This new clock isn’t a “relative timer” in the traditional sense, but it is never decreased, and it is never modified by date or time changes on the system. It increases at a constant rate. A “relative time” wait is nothing more than taking the current absolute value of the CLOCK_MONOTONIC clock, adding some fixed offset (4200 seconds for a wait of 1 hour and 10 minutes), and waiting until that value of the clock is reached.

This is accomplished by adding the condition variable attribute clock. You set the clock attribute in a condition variable attributes object using pthread_condattr_setclock and request the current value by calling pthread_condattr_getclock. The default value is CLOCK_MONOTONIC, on the assumption that most condition waits are intervals.

While this assumption may be incorrect, and it may seem to be an incompatible change from Pthreads (and it is, in a way), this was swept under the rug due to the fact that the timed condition wait function suffered from a problem that POSIX.1j found to be extremely common through the existing body of POSIX standards. “Time” in general was only very loosely defined. A timed condition wait, for example, does not say precisely what the timeout argument means. Only that “an error is returned if the absolute time specified by abstime passes (that is, system time equals or exceeds abstime).” The intent is clear—but there are no specific implementation or usage directives. One might reasonably assume that one should acquire the current time using clock_gettime (CLOCK_REALTIME,&now), as suggested in the associated rationale. However, POSIX “rationale” is little more than historical commentary, and is not part of the formal standard. Furthermore, clock_gettime is a part of the optional _POSIX_TIMERS subset of POSIX.1b, and therefore may not exist on many systems supporting threads.

POSIX.1j is attempting to “rationalize” all of these loose ends, at least for systems that implement the eventual POSIX.1j standard. Of course, the CLOCK_MONOTONIC feature is under an option of its own, and additionally relies on the _POSIX_TIMERS option, so it isn’t a cure-all. In the absence of these options, there is no clock attribute, and no way to be sure of relative timeout behavior—or even completely portable behavior.

Thread abort

The pthread_abort function is essentially fail-safe cancellation. It is used only when you want to be sure the thread will terminate immediately. The dangerous aspect of pthread_abort is that the thread does not run cleanup handlers or have any other opportunity to clean up after itself. That is, if the target thread has a mutex locked, the thread will terminate with the mutex still locked. Because you cannot unlock the mutex from another thread, the application must be prepared to abandon that mutex entirely. Further, it means that any other threads that might be waiting for the abandoned mutex will continue to wait for the mutex forever unless they are also terminated by calling pthread_abort.

In general, real applications cannot recover from aborting a thread, and you should never, ever, use pthread_abort. However, for a certain class of applications this capability is required. Imagine, for example, a realtime embedded control system that cannot shut down and must run reliably across any transient failure in some algorithm. Should a thread encounter a rare boundary condition bug, and hang, the application must recover.

In such a system, all wait operations use timeouts, because realtime response is critical. Should one thread detect that something hasn’t happened in a reasonable time, for example, a navigational thread hasn’t received sensor input, it will notify an “error manager.” If the error manager cannot determine why the thread monitoring the sensor hasn’t responded, it will try to recover. It may attempt to cancel the sensor thread to achieve a safe shutdown, but if the sensor thread fails to respond to the cancel in a reasonable time, the application must continue anyway. The error manager would then abort the sensor thread, analyze and correct any data structures it might have corrupted, create and advertise new mutexes if necessary, and create a new sensor thread.

POSIX 1003.14

POSIX.14 is a different sort of standard, a “POSIX Standard profile.” Unlike Pthreads and POSIX.1j, POSIX.14 does not add any new capabilities to the POSIX family. Instead, it attempts to provide some order to the maze of options that faces implementors and users of POSIX.

The POSIX.14 specifies which POSIX optional behavior should be considered “required” for multiprocessor hardware systems. It also raises some of the minimum values defined for various POSIX limits. The POSIX.14 working group also devised recommendations for additional POSIX interfaces based on the substantial multiprocessing and threading experience of the members. Many of the interfaces developed by POSIX.14 have been included in the POSIX.1j draft standard.

Once POSIX.14 becomes a standard, in theory, producers of POSIX implementations will be able to claim conformance to POSIX.14. And those who wish to develop multithreaded applications may find it convenient to look for POSIX.14 conformance rather than simply Pthreads conformance. (It remains to be seen whether vendors or users will actually do this, since experience with POSIX Standard Profiles is currently slight.)

The POSIX.14 working group also tried to address important issues such as these:

  • Providing a way for threaded code to determine the number of active processors.

  • Providing a way for threads to be “bound” onto physical processors.

  • Providing a “processor management” command to control which processors are used by the system.

Although these capabilities are universally available in all multiprocessor systems of which the working group was aware, they were dropped from the standard because of many unresolved issues, including these:

  • What good does it do to know how many processors there are, if you cannot tell how many your code may use at any time? Remember, the information can change while you are asking for it. What is really needed is a function asking the question “Would the current process benefit from creation of another thread?” We don’t know how to answer that question, or how to provide enough information on all reasonable architectures that the application can answer it.

  • How can we bind a thread to a processor across a wide range of multiprocessor architecture? On a nonuniform memory access system, for example, representing the processors as a uniform array of integer identifiers would be misleading and useless—binding one thread to processor 0 and another closely cooperative thread to processor 1 might put them across a relatively slow communications port rather than on two processors sharing a bank of memory.

Eventually, some standards organization (possibly POSIX) will need to address these issues and develop portable interfaces. The folks who attempt this feat may find that they need to limit the scope of the standard to a field narrower than “systems on which people may wish to use threads.”



[*] DCE threads implemented fast mutexes much like the definition of XSH5 normal mutexes, with no error checking. This was not, however, specification of intent.

[*] The POSIX working group is considering the possibility of adapting the XSH5 read/write lock definition and abandoning the original POSIX.1j names, but the decision hasn’t yet been made.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.199.243