HPlogo HP-UX Process Management: White Paper > Chapter 1 Process Management

Basic Threads Management

» 

Technical documentation

Complete book in PDF

 » Table of Contents

NOTE: Detailed coverage of threads management is beyond the scope of this white paper. For detailed information, consult ThreadTime: The Multithreaded Programming Guide, by Scott Norton and Mark DiPasquale, published by Prentice Hall under their Hewlett-Packard Professional Books imprint.

The life cycle of a thread is analogous to that of a process. When you fork() a process, an initial thread is created. To take advantage of the kernel's ability to run flows of execution either concurrently or (on multiprocessor systems) in parallel, you will want to create additional threads. The following chart pairs the process-oriented function with its threads-oriented API function. In all cases, pthread functions are prefaced "pthread_"; in several cases, threads routines have no process equivalent.

For complete specification, consult the manpages for pthread API functions in section three of the online reference.

Table 1-10 Comparison of thread API and process functions

Thread API functionProcess function
pthread_create()fork(), exec()
pthread_detach()<none>
pthread_join()wait()
pthread_exit()exit()
pthread_self()getpid()
pthread_equal()pid1 == pid2
pthread_kill()kill()
pthread_sigmask()sigprocmask()
pthread_suspend()<none>
pthread_resume()<none>
pthread_setschedparam()sched_setscheduler() sched_setparam()
pthread_getschedparam()sched_getscheduler() sched_getparam()
sched_yield()sched_yield()

 

Pthread APIs consist of the following:

  • 33 thread management functions

  • 46 thread synchronization functions

  • 15 thread scheduling functions

The following header files have definitions pertinent to threads:

  • pthread.h

  • sched.h

  • signal.h

Pthread API functions operate by the following rules:

  • There is no parent/child relationship between threads.

  • Any thread can make an independent system call or library call.

  • pthread_*() functions return 0 on success.

  • pthread_*() functions return an error number on failure. errno is not set.

  • You must use -D_REENTRANT when compiling a multi-threaded program:

    % cc -D_REENTRANT abc.c -lpthread

Thread Creation Overview

Thread creation resembles process creation. The new thread starts execution at start_routine with arg as its sole parameter. The new thread's ID is returned to the creator of the thread.

A thread has attributes that can be initialized prior to its creation. These include stack size, scheduling policy, and priority. For the newly created thread to have non-default attributes, you must pass a threads attribute object to pthread_create(). You can use the same threads attribute object to create multiple threads.

NOTE: After the thread is created, it is recommended that you destroy the attribute object to save memory.

To create a new thread, the user or threads library must allocate the user stack. The kernel does not allocate stacks for threads, other than the initial thread.

Thread Termination Overview

After a thread executes, it has two options:

  • Return from its start_routine. An implicit call to pthread_exit() is made with the function's return value. (Note, the initial thread should not do this. An implicit call to exit() is made by the initial thread on return.)

  • Call pthread_exit() to explicitly self-terminate.

HP-UX Threads Extensions

In addition to the POSIX threads functions, HP-UX provides the extensions shown in the following table. These non-standardized interfaces are subject to change; efforts are being made to standardize them through X/Open.

Table 1-11 Additional HP threads routines

HP threads ExtensionPurpose
pthread_suspend()Suspends a thread and blocks until the target thread has been suspended. Each time a thread is suspended, its suspension count is incremented.
pthread_continueResumes execution of a suspended thread.
pthread_resume_np()An HP-only function that provides control over how a thread is resumed by a flags field.
pthread_num_processors_np()Returns number of processors installed on the system.
pthread_processor_bind_np()Binds thread to processor.
pthread_processor_id_np()Identifies a specific processor on the system.

 

Thread Synchronization

POSIX provides four sets of synchronization primitives from which all other synchronization types can be built:

  • mutual exclusion locks (mutex)

  • condition variables

  • semaphores

  • read/write locks

All synchronization is "advisory," meaning that the programmer has the ultimate responsibility for making it work.

Synchronization objects can be process-local (used to synchronize threads within a process) or system visible (used by threads within different processes, such as by using shared memory or memory mapped files).

Synchronization operations perform as follows:

  • The init function initializes, but does not allocate, except "internal" resources.

  • The destroy function destroys all but the "internal" resources.

  • The lock function acquires an object, or if the object is unavailable, waits.

  • The try function returns EBUSY if the resource is being used or 0 if the resource is acquired.

  • The timed function awaits a synchronization signal for an absolute time or returns ETIMEDOUT if absolute time elapses.

  • The unlock function releases an acquired resource.

The simplest mechanism for synchronizing threads is to use pthread_join(), which waits fora thread to terminate. However, this function is insufficient for multithreaded cases in which threads must synchronize access to shared resources and data structures.

mutex Locks

Mutual exclusion (mutex) locks allow threads to synchronize access to process resources and shared objects, such as global data. Threads wishing to access an object locked by a mutex will block until the thread holding the object releases it.

mutex locks have attributes objects that can be set, based on the following characteristics:

pshared

Indicates if the mutex is shared by multiple processes or is local to the calling process. Valid values are PTHREAD_PROCESS_PRIVATE (default) and PTHREAD_PROCESS_SHARED.

how

Tells how a locking thread should block if it cannot acquire the mutex. Valid values govern whether or not the pthread should block and/or spin in a loop while attempting to acquire the mutex. Default behavior, governed by PTHREAD_LIMITED_SPIN_NP, asserts that the thread spin in a loop attempting to acquire the mutex; if not acquired after some determined number of iterations, block until the mutex can be acquired.

kind

Type of mutex. By default (PTHREAD_MUTEX_FAST_NP), the mutex is locked and unlocked in the fastest possible manner and no owner is maintained. Note, however, this can result in deadlock. Other valid values handle the mutex as recursive or nonrecursive and set rules for ownership and relocking.

Lock Order

When more than one synchronization variable is required by the same thread at the same time, lock order or hierarchy is vital to prevent deadlock. It is the programmer's responsibility to define the order of lock acquisition.

Condition Variables

Using a synchronization type called a condition variable, a thread can wait until or indicate that a predicate becomes true. A condition variable requires a mutex to protect the data associated with the predicate.

A condition wait has two forms:

  • absolute wait, until the condition occurs

  • timed wait, until the condition occurs or the absolute wait time has elapsed.

The condition wait operation releases the mutex, blocks waiting for the condition to signaled, at which time it reacquires the mutex.

A condition signal operation has two forms:

  • Signal a single waiter to wake up

  • Broadcast to all waiter to wake up

NOTE: Be cautious about doing a broadcast wake-up, as all threads awakened must reacquire the associated mutex. This can degrade performance.

Condition variables have only one attribute -- pshared. This indicates whether the condition variable is local to the calling process (the default, PTHREAD_PROCESS_PRIVATE) or shared by multiple processes. If shared, the caller must allocate the condition variable in shared memory.

Semaphores

POSIX.1b named and unnamed semaphores have been "tuned" specifically for threads.

The semaphore is initialized to a certain value and decremented. Threads may wait to acquire a semaphore.

  • If the current value of the semaphore is grater than 0, it is decremented and the wait call returnes.

  • If the value is 0 or less, the thread blocks until the semaphore is available.

NOTE: The POSIX.1b semaphores (both named and unnamed) were standardized before the POSIX threads standard. Consequently, they return errors in traditional UNIX fashion (0 == success, -1 with errno == failure). These semantic apply to all types of semaphores supported by HP-UX.

Read/Write Locks

These locks, a variant of the mutex lock, are useful when you have protected data that is read often but only occasionally written. Performance is slower than for a mutex.

Read/write locks allow for any number of concurrent readers and no writers or a single write and no readers. Once a writer has access, readers are blocked from access until the writer releases the lock. If both readers and writers are waiting for a lock, the released lock is given to a writer.

Read/write locks have two initializable attributes:

pshared

Indicates if the read/write lock is shared by multiple processes or is local to the calling process. Valid values are PTHREAD_PROCESS_PRIVATE (default) and PTHREAD_PROCESS_SHARED.

how

Tells how a locking thread should block if it cannot acquire the read/write lock. Valid values govern whether or not the pthread should block and/or spin in a loop while attempting to acquire the mutex. Default behavior, governed by PTHREAD_LIMITED_SPIN_NP, asserts that the thread spin in a loop attempting to acquire the read/write lock; if not acquired after some determined number of iterations, block until the read/write lock can be acquired.

Signal Handling

There are two types of signals:

  • Synchronous signals, which are generated as a result of some action taken by a thread at a given instant, such as an illegal instruction or dividing by zero.

    Synchronous signals are sent directly to the thread that caused the signal to be generated.

  • Asynchronous signals, which are generated due to an external event, such as kill() or timer expirations.

    Asynchronous signals are sent to the process. A single thread within the process that does not have the signal blocked will handle the signal.

Each thread has a signal mask used to block signals from being delivered to the thread. To examine or change the current thread's signal mask, use pthread_sigmask(), a function that behaves just like sigprocmask() in the process model. Do not use sigprocmask() to change the signal mask of a thread.

Each process contains a signal vector that describes what to do for each signal (for example, ignore, default, or execute a handler). This signal vector is shared by all threads in the process. There may be only one signal handler for any given signal. This handler is used by all threads in the process.

Each signal sent to a process is delivered once and only once to one thread within the process. Signals cannot be "broadcast" to all threads in a process.

The sigwait() function

A POSIX function, sigwait(), allows a thread to wait for a signal to be delivered in a multi-threaded application. This is easier than installing signal handlers to handle the signal when it arrives and dealing with interrupted system calls.

To wait for a signal, use

int sigwait(sigset_t *set, int *signal);

set is the set of signals being waited for, which must be blocked before calling sigwait. When a signal in set is delivered, this function returns and the signal being delivered is returned in signal.

Thread Cancellation

Threads may cancel or terminate other threads within their process.

Threads targetted for cancellation may hold cancellation requests pending, similar to how signals are blocked and held pending.

A thread's cancellation "state" determines whether cancellation is enabled or disabled (the latter blocks all requests). Valid states are PTHREAD_CANCEL_ENABLE and PTHREAD_CANCEL_DISABLE.

A thread's cancellation type determines when a cancellation request is acted upon (that is, when the thread is terminated). A value of PTHREAD_CANCEL_DEFERRED (default) holds cancellation requests pending until the thread enters a function that is a cancellation point. If set to PTHREAD_CANCEL_ASYNCHRONOUS, the thread can be cancelled at any moment.

NOTE: When a thread is cancelled, any mutexes, attribute objects, or other resources it is consuming are not released. This can cause application deadlock later! To remedy this, a thread may install cancellation cleanup handlers to release resources in the event it is cancelled.

Thread cancellation cleanup handlers resemble signal handlers. However, a thread may have multiple handlers installed As a thread leaves a non-cancel-safe section, the cancellation cleanup handlers are removed. Any installed cancellation cleanup handlers are executed when

  • The thread is cancelled.

  • The thread self-terminates (with pthread_exit()).

  • The handler is removed (if you specify execute).

A function safe from cancellation is one that does not contain a cancellation point nor call a function that is a cancellation point.

NOTE: Library routines must assume the application to which it is linked uses thread cancellation and protect itself.

Use the following thread cancellation cleanup handlers in your code:

pthread_cleanup_push()

Use for a thread before it enters a section of code that can be canceled.

pthread_cleanup_pop()

Remove the handler when the thread is finished executing the code that can be cancelled.

pthread_cancel()

Cancel a thread or request that it terminate itself. This function does not wait for the thread to terminate.

pthread_testcancel()

Use to test for a cancellation request and act upon it before performing time-consuming "critical" action. A thread can create a cancellation point. This function returns if no cancellation requests are pending; otherwise it does not return and the thread terminates.

pthread_setcancelstate()

Use to change a thread's state of cancellation.

pthread_setcanceltype()

Use to change a thread's type of cancellation. These latter two functions can be very handy when entering a section of code that cannot tolerate being interrupted or terminated.