Multithreaded Programming Guide

Chapter 6 Programming With Solaris Threads

This chapter compares the application programming interface (API) for Solaris and POSIX threads, and explains the Solaris features that are not found in POSIX threads. The chapter discusses the following topics:

Comparing APIs for Solaris Threads and POSIX Threads

The Solaris threads API and the pthreads API are two solutions to the same problem: build parallelism into application software. Although each API is complete, you can safely mix Solaris threads functions and pthread functions in the same program.

The two APIs do not match exactly, however. Solaris threads support functions that are not found in pthreads, and pthreads include functions that are not supported in the Solaris interface. For those functions that do match, the associated arguments might not, although the information content is effectively the same.

By combining the two APIs, you can use features not found in one API to enhance the other API. Similarly, you can run applications that use Solaris threads exclusively with applications that use pthreads exclusively on the same system.

Major API Differences

Solaris threads and pthreads are very similar in both API action and syntax. The major differences are listed in Table 6–1 .

Table 6–1 Unique Solaris Threads and pthreads Features

Solaris Threads 

POSIX Threads 

thr_ prefix for threads function names, sema_ prefix for semaphore function names

pthread_ prefix for pthreads function names, sem_ prefix for semaphore function names

Ability to create “daemon” threads 

Cancellation semantics 

Suspending and continuing a thread 

Scheduling policies 

Function Comparison Table

The following table compares Solaris threads functions with pthreads functions. Note that even when Solaris threads and pthreads functions appear to be essentially the same, the arguments to the functions can differ.

When a comparable interface is not available either in pthreads or Solaris threads, a hyphen `-' appears in the column. Entries in the pthreads column that are followed by (3RT) are functions in librt, the POSIX.1b Realtime Extensions library, which is not part of pthreads. Functions in this library provide most of the interfaces specified by the POSIX.1b Realtime Extension.

Table 6–2 Solaris Threads and POSIX pthreads Comparison

Solaris Threads 

pthreads 

thr_create()

pthread_create()

thr_exit()

pthread_exit()

thr_join()

pthread_join()

thr_yield()

sched_yield()(3RT)

thr_self()

pthread_self()

thr_kill()

pthread_kill()

thr_sigsetmask()

pthread_sigmask()

thr_setprio()

pthread_setschedparam()

thr_getprio()

pthread_getschedparam()

thr_setconcurrency()

pthread_setconcurrency()

thr_getconcurrency()

pthread_getconcurrency()

thr_suspend()

thr_continue()

thr_keycreate()

pthread_key_create()

pthread_key_delete()

thr_setspecific()

pthread_setspecific()

thr_getspecific()

pthread_getspecific()

pthread_once()

pthread_equal()

pthread_cancel()

pthread_testcancel()

pthread_cleanup_push()

pthread_cleanup_pop()

pthread_setcanceltype()

pthread_setcancelstate()

mutex_lock()

pthread_mutex_lock()

mutex_unlock()

pthread_mutex_unlock()

mutex_trylock()

pthread_mutex_trylock()

mutex_init()

pthread_mutex_init()

mutex_destroy()

pthread_mutex_destroy()

cond_wait()

pthread_cond_wait()

cond_timedwait()

pthread_cond_timedwait()

cond_reltimedwait()

pthread_cond_reltimedwait_np()

cond_signal()

pthread_cond_signal()

cond_broadcast()

pthread_cond_broadcast()

cond_init()

pthread_cond_init()

cond_destroy()

pthread_cond_destroy()

rwlock_init()

pthread_rwlock_init()

rwlock_destroy()

pthread_rwlock_destroy()

rw_rdlock()

pthread_rwlock_rdlock()

rw_wrlock()

pthread_rwlock_wrlock()

rw_unlock()

pthread_rwlock_unlock()

rw_tryrdlock()

pthread_rwlock_tryrdlock()

rw_trywrlock()

pthread_rwlock_trywrlock()

pthread_rwlockattr_init()

pthread_rwlockattr_destroy()

pthread_rwlockattr_getpshared()

pthread_rwlockattr_setpshared()

sema_init()

sem_init()(3RT)

sema_destroy()

sem_destroy()(3RT)

sema_wait()

sem_wait()(3RT)

sema_post()

sem_post()(3RT)

sema_trywait()

sem_trywait()(3RT)

fork1()

fork()

pthread_atfork()

forkall(), multiple thread copy

pthread_mutexattr_init()

pthread_mutexattr_destroy()

type argument in mutex_init()

pthread_mutexattr_setpshared()

pthread_mutexattr_getpshared()

pthread_mutex_attr_settype()

pthread_mutex_attr_gettype()

pthread_condattr_init()

pthread_condattr_destroy()

type argument in cond_init()

pthread_condattr_setpshared()

pthread_condattr_getpshared()

pthread_attr_init()

pthread_attr_destroy()

THR_BOUND flag in thr_create()

pthread_attr_setscope()

pthread_attr_getscope()

pthread_attr_setguardsize()

pthread_attr_getguardsize()

stack_size argument in thr_create()

pthread_attr_setstacksize()

pthread_attr_getstacksize()

stack_addr argument in thr_create()

pthread_attr_setstack()

pthread_attr_getstack()

THR_DETACH flag in thr_create()

pthread_attr_setdetachstate()

pthread_attr_getdetachstate()

pthread_attr_setschedparam()

pthread_attr_getschedparam()

pthread_attr_setinheritsched()

pthread_attr_getinheritsched()

pthread_attr_setsschedpolicy()

pthread_attr_getschedpolicy()

To use the Solaris threads functions described in this chapter for Solaris 9 and previous releases, you must link with the Solaris threads library -lthread .

Operation is virtually the same for both Solaris threads and for pthreads, even though the function names or arguments might differ. Only a brief example consisting of the correct include file and the function prototype is presented. Where return values are not given for the Solaris threads functions, see the appropriate pages in man pages section 3: Basic Library Functions for the function return values.

For more information on Solaris related functions, see the related pthreads documentation for the similarly named function.

Where Solaris threads functions offer capabilities that are not available in pthreads, a full description of the functions is provided.

Unique Solaris Threads Functions

This section describes unique Solaris threads functions: suspending thread execution and continuing a suspended thread.

Suspending Thread Execution

thr_suspend(3C) immediately suspends the execution of the thread specified by target_thread. On successful return from thr_suspend(), the suspended thread is no longer executing.

Because thr_suspend()suspends the target thread with no regard to the locks that the thread might be holding, you must use thr_suspend() with extreme care. If the suspending thread calls a function that requires a lock held by the suspended target thread, deadlock will result.

thr_suspend Syntax

#include <thread.h>

int thr_suspend(thread_t tid);

After a thread is suspended, subsequent calls to thr_suspend() have no effect. Signals cannot awaken the suspended thread. The signals remain pending until the thread resumes execution.

In the following synopsis, pthread_t tid as defined in pthreads is the same as thread_t tid in Solaris threads. tid values can be used interchangeably either by assignment or through the use of casts.

thread_t tid; /* tid from thr_create() */

/* pthreads equivalent of Solaris tid from thread created */
/* with pthread_create() */
pthread_t ptid; 

int ret;

ret = thr_suspend(tid);

/* using pthreads ID variable with a cast */
ret = thr_suspend((thread_t) ptid); 

thr_suspend Return Values

thr_suspend() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, thr_suspend() fails and returns the corresponding value.


ESRCH

Description:

tid cannot be found in the current process.

Continuing a Suspended Thread

thr_continue(3C) resumes the execution of a suspended thread. Once a suspended thread is continued, subsequent calls to thr_continue() have no effect.

thr_continue Syntax

#include <thread.h>

int thr_continue(thread_t tid);

A suspended thread is not awakened by a signal. The signal remains pending until the execution of the thread is resumed by thr_continue() .

pthread_t tid as defined in pthreads is the same as thread_t tid in Solaris threads. tid values can be used interchangeably either by assignment or through the use of casts.

thread_t tid; /* tid from thr_create()*/

/* pthreads equivalent of Solaris tid from thread created */
/* with pthread_create()*/
pthread_t ptid;

int ret;

ret = thr_continue(tid);

/* using pthreads ID variable with a cast */
ret = thr_continue((thread_t) ptid) 

thr_continue Return Values

thr_continue() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, thr_continue() fails and returns the corresponding value.


ESRCH

Description:

tid cannot be found in the current process.

Similar Synchronization Functions—Read-Write Locks

Read-write locks allow simultaneous read access by many threads while restricting write access to only one thread at a time. This section discusses the following topics:

When any thread holds the lock for reading, other threads can also acquire the lock for reading but must wait to acquire the lock for writing. If one thread holds the lock for writing, or is waiting to acquire the lock for writing, other threads must wait to acquire the lock for either reading or writing.

Read-write locks are slower than mutexes. But read-write locks can improve performance when the locks protect data not frequently written but are read by many concurrent threads.

Use read-write locks to synchronize threads in this process and other processes. Allocate read-write locks in memory that is writable and shared among the cooperating processes. See themmap(2) man page for information about mapping read-write locks for this behavior.

By default, the acquisition order is not defined when multiple threads are waiting for a read-write lock. However, to avoid writer starvation, the Solaris threads package tends to favor writers over readers of equal priority.

Read-write locks must be initialized before use.

Initialize a Read-Write Lock

Use rwlock_init(3C) to initialize the read-write lock pointed to by rwlp and to set the lock state to unlocked.

rwlock_init Syntax

#include <synch.h>  (or #include <thread.h>)

int rwlock_init(rwlock_t *rwlp, int type, void * 
arg);

type can be one of the following values:

Multiple threads must not initialize the same read-write lock simultaneously. Read-write locks can also be initialized by allocation in zeroed memory, in which case a type of USYNC_THREAD is assumed. A read-write lock must not be reinitialized while other threads might be using the lock.

For POSIX threads, see pthread_rwlock_init Syntax .

Initializing Read-Write Locks With Intraprocess Scope

#include <thread.h> 
rwlock_t rwlp; 
int ret; 
/* to be used within this process only */ 
ret = rwlock_init(&rwlp, USYNC_THREAD, 0); 

Initializing Read-Write Locks With Interprocess Scope

#include <thread.h> 
rwlock_t rwlp; 
int ret; 
/* to be used among all processes */ 
ret = rwlock_init(&rwlp, USYNC_PROCESS, 0); 

rwlock_init Return Values

rwlock_init() returns zero after completing successfully. Any other return value indicates that an error occurred. When any of the following conditions occurs, the function fails and returns the corresponding value.


EINVAL

Description:

Invalid argument.


EFAULT

Description:

rwlp or arg points to an illegal address.

Acquiring a Read Lock

Use rw_rdlock(3C) to acquire a read lock on the read-write lock pointed to by rwlp.

rw_rdlock Syntax

#include <synch.h> (or #include <thread.h>)

int rw_rdlock(rwlock_t *rwlp);

When the read-write lock is already locked for writing, the calling thread blocks until the write lock is released. Otherwise, the read lock is acquired. For POSIX threads, see pthread_rwlock_rdlock Syntax.

rw_rdlock Return Values

rw_rdlock() returns zero after completing successfully. Any other return value indicates that an error occurred. When any of the following conditions occurs, the function fails and returns the corresponding value.


EINVAL

Description:

Invalid argument.


EFAULT

Description:

rwlp points to an illegal address.

Trying to Acquire a Read Lock

Use rw_tryrdlock(3C) to attempt to acquire a read lock on the read-write lock pointed to by rwlp.

rw_tryrdlock Syntax

#include <synch.h>  (or #include <thread.h>)

int rw_tryrdlock(rwlock_t *rwlp);

When the read-write lock is already locked for writing, rw_tryrdlock() returns an error. Otherwise, the read lock is acquired. For POSIX threads, see pthread_rwlock_tryrdlock Syntax.

rw_tryrdlock Return Values

rw_tryrdlock() returns zero after completing successfully. Any other return value indicates that an error occurred. When any of the following conditions occurs, the function fails and returns the corresponding value.


EINVAL

Description:

Invalid argument.


EFAULT

Description:

rwlp points to an illegal address.


EBUSY

Description:

The read-write lock pointed to by rwlp was already locked.

Acquiring a Write Lock

Use rw_wrlock(3C) to acquire a write lock on the read-write lock pointed to by rwlp.

rw_wrlock Syntax

#include <synch.h>  (or #include <thread.h>)

int rw_wrlock(rwlock_t *rwlp);

When the read-write lock is already locked for reading or writing, the calling thread blocks until all read locks and write locks are released. Only one thread at a time can hold a write lock on a read-write lock. For POSIX threads, see pthread_rwlock_wrlock Syntax.

rw_wrlock Return Values

rw_wrlock() returns zero after completing successfully. Any other return value indicates that an error occurred. When any of the following conditions occurs, the function fails and returns the corresponding value.


EINVAL

Description:

Invalid argument.


EFAULT

Description:

rwlp points to an illegal address.

Trying to Acquire a Write Lock

Use rw_trywrlock(3C) to attempt to acquire a write lock on the read-write lock pointed to by rwlp.

rw_trywrlock Syntax

#include <synch.h>  (or #include <thread.h>)

int rw_trywrlock(rwlock_t *rwlp);

When the read-write lock is already locked for reading or writing, rw_trywrlock() returns an error. For POSIX threads, see pthread_rwlock_trywrlock Syntax.

rw_trywrlock Return Values

rw_trywrlock() returns zero after completing successfully. Any other return value indicates that an error occurred. When any of the following conditions occurs, the function fails and returns the corresponding value.


EINVAL

Description:

Invalid argument.


EFAULT

Description:

rwlp points to an illegal address.


EBUSY

Description:

The read-write lock pointed to by rwlp was already locked.

Unlock a Read-Write Lock

Use rw_unlock(3C) to unlock a read-write lock pointed to by rwlp.

rw_unlock Syntax

#include <synch.h>  (or #include <thread.h>)

int rw_unlock(rwlock_t *rwlp);

The read-write lock must be locked, and the calling thread must hold the lock either for reading or writing. When any other threads are waiting for the read-write lock to become available, one of the threads is unblocked. For POSIX threads, see pthread_rwlock_unlock Syntax.

rw_unlock Return Values

rw_unlock() returns zero after completing successfully. Any other return value indicates that an error occurred. When any of the following conditions occurs, the function fails and returns the corresponding value.


EINVAL

Description:

Invalid argument.


EFAULT

Description:

rwlp points to an illegal address.

Destroying the Read-Write Lock State

Use rwlock_destroy(3C) to destroy any state that is associated with the read-write lock pointed to by rlwp.

rwlock_destroy Syntax

#include <synch.h>  (or #include <thread.h>)

int rwlock_destroy(rwlock_t *rwlp);

The space for storing the read-write lock is not freed. For POSIX threads, see pthread_rwlock_destroy Syntax.

Example 6–1 uses a bank account to demonstrate read-write locks. While the program could allow multiple threads to have concurrent read-only access to the account balance, only a single writer is allowed. Note that the get_balance() function needs the lock to ensure that the addition of the checking and saving balances occurs atomically.


Example 6–1 Read-Write Bank Account

rwlock_t account_lock;
float checking_balance = 100.0;
float saving_balance = 100.0;
...
rwlock_init(&account_lock, 0, NULL);
...

float
get_balance() {
    float bal;

    rw_rdlock(&account_lock);
    bal = checking_balance + saving_balance;
    rw_unlock(&account_lock);
    return(bal);
}

void
transfer_checking_to_savings(float amount) {
    rw_wrlock(&account_lock);
    checking_balance = checking_balance - amount;
    saving_balance = saving_balance + amount;
    rw_unlock(&account_lock);
}

rwlock_destroy Return Values

rwlock_destroy() returns zero after completing successfully. Any other return value indicates that an error occurred. When any of the following conditions occurs, the function fails and returns the corresponding value.


EINVAL

Description:

Invalid argument.


EFAULT

Description:

rwlp points to an illegal address.

Similar Solaris Threads Functions

Table 6–3 Similar Solaris Threads Functions

Operation 

Related Function Description 

Create a thread 

thr_create Syntax

Get the minimal stack size 

thr_min_stack Syntax

Get the thread identifier 

thr_self Syntax

Yield thread execution 

thr_yield Syntax

Send a signal to a thread 

thr_kill Syntax

Access the signal mask of the calling thread 

thr_sigsetmask Syntax

Terminate a thread 

thr_exit Syntax

Wait for thread termination 

thr_join Syntax

Create a thread-specific data key 

thr_keycreate Syntax

Set thread-specific data 

thr_setspecific Syntax

Get thread-specific data 

thr_getspecific Syntax

Set the thread priority 

thr_setprio Syntax

Get the thread priority 

thr_getprio Syntax

Creating a Thread

The thr_create(3C) routine is one of the most elaborate of all routines in the Solaris threads interface.

Use thr_create(3C) to add a new thread of control to the current process. For POSIX threads, see pthread_create Syntax.

thr_create Syntax

#include <thread.h>

int thr_create(void *stack_base, size_t stack_size,
          void *(*start_routine) (void *), void *arg, 
          long flags,
          thread_t *new_thread);

size_t thr_min_stack(void);

Note that the new thread does not inherit pending signals, but the thread does inherit priority and signal masks.

stack_base. Contains the address for the stack that the new thread uses. If stack_base is NULL, then thr_create() allocates a stack for the new thread with at least stack_size bytes.

stack_size . Contains the size, in number of bytes, for the stack that the new thread uses. If stack_size is zero, a default size is used. In most cases, a zero value works best. If stack_size is not zero, stack_size must be greater than the value returned by thr_min_stack().

In general, you do not need to allocate stack space for threads. The system allocates 1 megabyte of virtual memory for each thread's stack with no reserved swap space. The system uses the -MAP_NORESERVE option of mmap(2) to make the allocations.

start_routine. Contains the function with which the new thread begins execution. When start_routine() returns, the thread exits with the exit status set to the value returned by start_routine . See thr_exit Syntax.

arg. Can be any variable described by void , which is typically any 4-byte value. Any larger value must be passed indirectly by having the argument point to the variable.

Note that you can supply only one argument. To get your procedure to take multiple arguments, encode the multiple arguments as a single argument, such as by putting the arguments in a structure.

flags. Specifies attributes for the created thread. In most cases a zero value works best.

The value in flags is constructed from the bitwise inclusive OR of the following arguments:


Note –

When no explicit synchronization is allocated, an unsuspended, detached thread can fail. On failure, the thread ID is reassigned to another new thread before its creator returns from thr_create().


new_thread. When new_thread is not NULL, it points to where the ID of the new thread is stored when thr_create() is successful. The caller is responsible for supplying the storage pointed to by this argument. The ID is valid only within the calling process.

If you are not interested in this identifier, supply a NULL value to new_thread.

thr_create Return Values

thr_create() returns zero when the function completes successfully. Any other return value indicates that an error occurred. When any of the following conditions is detected, thr_create() fails and returns the corresponding value.


EAGAIN

Description:

A system limit is exceeded, such as when too many LWPs have been created.


ENOMEM

Description:

Insufficient memory was available to create the new thread.


EINVAL

Description:

stack_base is not NULL and stack_size is less than the value returned by thr_min_stack.()

Getting the Minimal Stack Size

Use thr_min_stack(3C) to get the minimum stack size for a thread.

Stack behavior in Solaris threads is generally the same as stack behavior in pthreads. For more information about stack setup and operation, see About Stacks.

thr_min_stack Syntax

#include <thread.h>

size_t thr_min_stack(void);

thr_min_stack() returns the amount of space that is needed to execute a null thread. A null thread is a thread that is created to execute a null procedure. Useful threads need more than the absolute minimum stack size, so be very careful when reducing the stack size.

A thread that executes more than a null procedure should allocate a stack size that is larger than the size of thr_min_stack().

When a thread is created with a user-supplied stack, the user must reserve enough space to run the thread. A dynamically linked execution environment increases the difficulty of determining the thread minimal stack requirements.

You can specify a custom stack in two ways. The first is to supply a NULL for the stack location, thereby asking the runtime library to allocate the space for the stack, but to supply the desired size in the stacksize parameter to thr_create() .

The other approach is to take overall aspects of stack management and supply a pointer to the stack to thr_create(). This means that you are responsible not only for stack allocation but also for stack deallocation. When the thread terminates, you must arrange for the disposal of the thread's stack.

When you allocate your own stack, be sure to append a red zone to its end by calling mprotect(2).

Most users should not create threads with user-supplied stacks. User-supplied stacks exist only to support applications that require complete control over their execution environments.

Instead, users should let the system manage stack allocation. The system provides default stacks that should meet the requirements of any created thread.

thr_min_stack Return Values

No errors are defined.

Acquiring the Thread Identifier

Use thr_self(3C) to get the ID of the calling thread. For POSIX threads, see pthread_self Syntax.

thr_self Syntax

#include <thread.h>

thread_t thr_self(void);

thr_self Return Values

No errors are defined.

Yield Thread Execution

thr_yield(3C) causes the current thread to yield its execution in favor of another thread with the same or greater priority. Otherwise, thr_yield() has no effect. However, calling thr_yield() does not guarantee that the thread yields its execution.

thr_yield Syntax

#include <thread.h>

void thr_yield(void);

thr_yield Return Values

thr_yield() returns nothing and does not set errno .

Send a Signal to a Thread

thr_kill(3C) sends a signal to a thread. For POSIX threads, see pthread_kill Syntax.

thr_kill Syntax

#include <thread.h>
#include <signal.h>
int thr_kill(thread_t target_thread, int sig);

thr_kill Return Values

Upon successful completion, thr_kill() returns 0. When any of the following conditions is detected, thr_kill() fails and returns the corresponding value. When a failure occurs, no signal is sent.


ESRCH

Description:

No thread was found associated with the thread designated by thread ID.


EINVAL

Description:

The sig argument value is not zero. sig is an invalid or unsupported signal number.

Access the Signal Mask of the Calling Thread

Use thr_sigsetmask(3C) to change or examine the signal mask of the calling thread.

thr_sigsetmask Syntax

#include <thread.h>
#include <signal.h>
int thr_sigsetmask(int how, const sigset_t *set, 
          sigset_t *oset);

thr_sigsetmask() changes or examines a calling thread's signal mask. Each thread has its own signal mask. A new thread inherits the calling thread's signal mask and priority. However, pending signals are not inherited. Pending signals for a new thread will be empty.

If the value of the argument set is not NULL, set points to a set of signals that can modify the currently blocked set. If the value of set is NULL, the value of how is insignificant and the thread's signal mask is unmodified. Use this behavior to inquire about the currently blocked signals.

The value of how specifies the method in which the set is changed. how takes one of the following values.

thr_sigsetmask Return Values

Upon successful completion, thr_sigsetmask() returns 0. When any of the following conditions is detected, thr_sigsetmask() fails and returns the corresponding value.


EINVAL

Description:

set is not NULL and the value of how is not defined.

Terminate a Thread

Use thr_exit(3C) to terminate a thread. For POSIX threads, see pthread_exit Syntax.

thr_exit Syntax

#include <thread.h>

void thr_exit(void *status);

thr_exit Return Values

thr_exit() does not return to its caller.

Wait for Thread Termination

Use thr_join(3C) to wait for a target thread to terminate. For POSIX threads, see pthread_join Syntax.

thr_join Syntax

#include <thread.h>

int thr_join(thread_t tid, thread_t *departedid, void **status);

The target thread must be a member of the current process. The target thread cannot be a detached thread or a daemon thread.

Several threads cannot wait for the same thread to complete. One thread will complete successfully. The others will terminate with an ESRCH error.

thr_join() will not block processing of the calling thread if the target thread has already terminated.

thr_join, Join Specific

#include <thread.h>

thread_t tid;
thread_t departedid;
int ret;
void *status;

/* waiting to join thread "tid" with status */
ret = thr_join(tid, &departedid, &status);

/* waiting to join thread "tid" without status */
ret = thr_join(tid, &departedid, NULL);

/* waiting to join thread "tid" without return id and status */
ret = thr_join(tid, NULL, NULL); 

When the tid is (thread_t)0, then thread_join() waits for any undetached thread in the process to terminate. In other words, when no thread identifier is specified, any undetached thread that exits causes thread_join() to return.

thr_join, Join Any

#include <thread.h>

thread_t tid;
thread_t departedid;
int ret;
void *status;

/* waiting to join any non-detached thread with status */
ret = thr_join(0, &departedid, &status); 

By indicating 0 as the thread ID in the Solaris thr_join(), a join takes place when any non detached thread in the process exits. The departedid indicates the thread ID of the exiting thread.

thr_join Return Values

thr_join() returns 0 if successful. When any of the following conditions is detected, thr_join() fails and returns the corresponding value.


ESRCH

Description:

No undetached thread is found which corresponds to the target thread ID.


EDEADLK

Description:

A deadlock was detected or the value of the target thread specifies the calling thread.

Creating a Thread-Specific Data Key

thr_keycreate(3C) allocates a key that is used to identify thread-specific data in a process. The key is global to all threads in the process. Each thread binds a value to the key when the key gets created.

Except for the function names and arguments, thread-specific data is the same for Solaris threads as thread-specific data is for POSIX threads. The synopses for the Solaris functions are described in this section. For POSIX threads, see pthread_key_create Syntax.

thr_keycreate Syntax

#include <thread.h>

int thr_keycreate(thread_key_t *keyp,
    void (*destructor) (void *value));

keyp independently maintains specific values for each binding thread. Each thread is initially bound to a private element of keyp that allows access to its thread-specific data. Upon key creation, a new key is assigned the value NULL for all active threads. Additionally, upon thread creation, all previously created keys in the new thread are assigned the value NULL.

An optional destructor function can be associated with each keyp. Upon thread exit, if a keyp has a non-NULL destructor and the thread has a non-NULL value associated with keyp , the destructor is called with the currently associated value. If more than one destructor exists for a thread when it exits, the order of destructor calls is unspecified.

thr_keycreate Return Values

thr_keycreate() returns 0 if successful. When any of the following conditions is detected, thr_keycreate() fails and returns the corresponding value.


EAGAIN

Description:

The system does not have the resources to create another thread-specific data key, or the number of keys exceeds the per-process limit for PTHREAD_KEYS_MAX.


ENOMEM

Description:

Insufficient memory is available to associate value with keyp.

Setting the Thread-Specific Data Value

thr_setspecific(3C) binds value to the thread-specific data key, key, for the calling thread. For POSIX threads, see pthread_setspecific Syntax.

thr_setspecific Syntax

#include <thread.h>

int thr_setspecific(thread_key_t key, void *value);

thr_setspecific Return Values

thr_setspecific() returns 0 if successful. When any of the following conditions is detected, thr_setspecific() fails and returns the corresponding value.


ENOMEM

Description:

Insufficient memory is available to associate value with keyp.


EINVAL

Description:

keyp is invalid.

Getting the Thread-Specific Data Value

thr_getspecific(3C) stores the current value bound to key for the calling thread into the location pointed to by valuep. For POSIX threads, see pthread_getspecific Syntax.

thr_getspecific Syntax

#include <thread.h>

int thr_getspecific(thread_key_t key, void **valuep);

thr_getspecific Return Values

thr_getspecific() returns 0 if successful. When any of the following conditions is detected, thr_getspecific() fails and returns the corresponding value.


ENOMEM

Description:

Insufficient memory is available to associate value with keyp.


EINVAL

Description:

keyp is invalid.

Set the Thread Priority

In Solaris threads, a thread created with a priority other than the priority of its parents is created in SUSPEND mode. While suspended, the thread's priority is modified using the thr_setprio(3C) function call. After thr_setprio() completes, the thread resumes execution.

A higher priority thread receives precedence over lower priority threads with respect to synchronization object contention.

thr_setprio Syntax

thr_setprio(3C) changes the priority of the thread, specified by tid, within the current process to the priority specified by newprio. For POSIX threads, see pthread_setschedparam Syntax.

#include <thread.h>

int thr_setprio(thread_t tid, int newprio)

The range of valid priorities for a thread depends on its scheduling policy.

thread_t tid;
int ret;
int newprio = 20;

/* suspended thread creation */
ret = thr_create(NULL, NULL, func, arg, THR_SUSPENDED, &tid);

/* set the new priority of suspended child thread */
ret = thr_setprio(tid, newprio);

/* suspended child thread starts executing with new priority */
ret = thr_continue(tid);

thr_setprio Return Values

thr_setprio() returns 0 if successful. When any of the following conditions is detected, thr_setprio() fails and returns the corresponding value.


ESRCH

Description:

The value specified by tid does not refer to an existing thread.


EINVAL

Description:

The value of priority is invalid for the scheduling policy of the specified thread.


EPERM

Description:

The caller does not have the appropriate permission to set the priority to the value specified.

Get the Thread Priority

Use thr_getprio(3C) to get the current priority for the thread. Each thread inherits a priority from its creator. thr_getprio() stores the current priority, tid, in the location pointed to by newprio. For POSIX threads, see pthread_getschedparam Syntax.

thr_getprio Syntax

#include <thread.h>

int thr_getprio(thread_t tid, int *newprio)

thr_getprio Return Values

thr_getprio() returns 0 if successful. When the following condition is detected, thr_getprio() fails and returns the corresponding value.


ESRCH

Description:

The value specified by tid does not refer to an existing thread.

Similar Synchronization Functions—Mutual Exclusion Locks

Initialize a Mutex

Use mutex_init(3C) to initialize the mutex pointed to by mp. For POSIX threads, see Initializing a Mutex.

mutex_init(3C) Syntax

#include <synch.h> 
#include <thread.h>

int mutex_init(mutex_t *mp, int type, void *arg)); 

The type can be one of the following values.

When a process fails while holding a USYNC_PROCESS lock, subsequent requestors of that lock hang. This behavior is a problem for systems that share locks with client processes because the client processes can be abnormally killed. To avoid the problem of hanging on a lock held by a dead process, use USYNC_PROCESS_ROBUST to lock the mutex. USYNC_PROCESS_ROBUST adds two capabilities:

Mutexes can also be initialized by allocation in zeroed memory, in which case a type of USYNC_THREAD is assumed.

Multiple threads must not initialize the same mutex simultaneously. A mutex lock must not be reinitialized while other threads might be using the mutex.

Mutexes With Intraprocess Scope

#include <thread.h>

mutex_t mp;
int ret;

/* to be used within this process only */
ret = mutex_init(&mp, USYNC_THREAD, 0); 

Mutexes With Interprocess Scope

#include <thread.h>

mutex_t mp;
int ret;

/* to be used among all processes */
ret = mutex_init(&mp, USYNC_PROCESS, 0); 

Mutexes With Interprocess Scope-Robust

#include <thread.h>

mutex_t mp;
int ret;

/* to be used among all processes */
ret = mutex_init(&mp, USYNC_PROCESS_ROBUST, 0); 

mutex_init Return Values

mutex_init() returns 0 if successful. When any of the following conditions is detected, mutex_init() fails and returns the corresponding value.


EFAULT

Description:

mp points to an illegal address.


EINVAL

Description:

The value specified by mp is invalid.


ENOMEM

Description:

System has insufficient memory to initialize the mutex.


EAGAIN

Description:

System has insufficient resources to initialize the mutex.


EBUSY

Description:

System detected an attempt to reinitialize an active mutex.

Destroy a Mutex

Use mutex_destroy(3C) to destroy any state that is associated with the mutex pointed to by mp . The space for storing the mutex is not freed. For POSIX threads, see pthread_mutex_destroy Syntax.

mutex_destroy Syntax

#include <thread.h>

int mutex_destroy (mutex_t *mp);

mutex_destroy Return Values

mutex_destroy() returns 0 if successful. When the following condition is detected, mutex_destroy() fails and returns the corresponding value.


EFAULT

Description:

mp points to an illegal address.

Acquiring a Mutex

Use mutex_lock(3C) to lock the mutex pointed to by mp. When the mutex is already locked, the calling thread blocks until the mutex becomes available. Blocked threads wait on a prioritized queue. For POSIX threads, see pthread_mutex_lock Syntax.

mutex_lock Syntax

#include <thread.h>

int mutex_lock(mutex_t *mp);

mutex_lock Return Values

mutex_lock() returns 0 if successful. When any of the following conditions is detected, mutex_lock() fails and returns the corresponding value.


EFAULT

Description:

mp points to an illegal address.


EDEADLK

Description:

The mutex is already locked and is owned by the calling thread.

Releasing a Mutex

Use mutex_unlock(3C) to unlock the mutex pointed to by mp. The mutex must be locked. The calling thread must be the thread that last locked the mutex, the owner. For POSIX threads, see pthread_mutex_unlock Syntax.

mutex_unlock Syntax

#include <thread.h>

int mutex_unlock(mutex_t *mp);

mutex_unlock Return Values

mutex_unlock() returns 0 if successful. When any of the following conditions is detected, mutex_unlock() fails and returns the corresponding value.


EFAULT

Description:

mp points to an illegal address.


EPERM

Description:

The calling thread does not own the mutex.

Trying to Acquire a Mutex

Use mutex_trylock(3C) to attempt to lock the mutex pointed to by mp. This function is a nonblocking version of mutex_lock(). For POSIX threads, see pthread_mutex_trylock Syntax.

mutex_trylock Syntax

#include <thread.h>

int mutex_trylock(mutex_t *mp);

mutex_trylock Return Values

mutex_trylock() returns 0 if successful. When any of the following conditions is detected, mutex_trylock() fails and returns the corresponding value.


EFAULT

Description:

mp points to an illegal address.


EBUSY

Description:

The system detected an attempt to reinitialize an active mutex.

Similar Synchronization Functions: Condition Variables

Initialize a Condition Variable

Use cond_init(3C) to initialize the condition variable pointed to by cv.

cond_init Syntax

#include <thread.h>

int cond_init(cond_t *cv, int type, int arg);

The type can be one of the following values:

Condition variables can also be initialized by allocation in zeroed memory, in which case a type of USYNC_THREAD is assumed.

Multiple threads must not initialize the same condition variable simultaneously. A condition variable must not be reinitialized while other threads might be using the condition variable.

For POSIX threads, see pthread_condattr_init Syntax .

Condition Variables With Intraprocess Scope

#include <thread.h>

cond_t cv;
int ret;

/* to be used within this process only */
ret = cond_init(cv, USYNC_THREAD, 0); 

Condition Variables With Interprocess Scope

#include <thread.h>

cond_t cv;
int ret;

/* to be used among all processes */
ret = cond_init(&cv, USYNC_PROCESS, 0); 

cond_init Return Values

cond_init() returns 0 if successful. When any of the following conditions is detected, cond_init() fails and returns the corresponding value.


EFAULT

Description:

cv points to an illegal address.


EINVAL

Description:

type is not a recognized type.

Destroying a Condition Variable

Use cond_destroy(3C) to destroy state that is associated with the condition variable pointed to by cv . The space for storing the condition variable is not freed. For POSIX threads, see pthread_condattr_destroy Syntax.

cond_destroy Syntax

#include <thread.h>

int cond_destroy(cond_t *cv);

cond_destroy Return Values

cond_destroy() returns 0 if successful. When any of the following conditions is detected, cond_destroy() fails and returns the corresponding value.


EFAULT

Description:

cv points to an illegal address.


EBUSY

Description:

The system detected an attempt to destroy an active condition variable.

Waiting for a Condition

Use cond_wait(3C) to atomically release the mutex pointed to by mp and cause the calling thread to block on the condition variable pointed to by cv. The blocked thread can be awakened by cond_signal(), cond_broadcast() , or when interrupted by delivery of a signal or a fork().

cond_wait() always returns with the mutex locked and owned by the calling thread, even when returning an error.

cond_wait Syntax

#include <thread.h>

int cond_wait(cond_t *cv, mutex_t *mp);

cond_wait Return Values

cond_wait() returns 0 if successful. When any of the following conditions is detected, cond_wait() fails and returns the corresponding value.


EFAULT

Description:

cv points to an illegal address.


EINTR

Description:

The wait was interrupted by a signal.

Wait for an Absolute Time

cond_timedwait(3C) is very similar to cond_wait(), except that cond_timedwait() does not block past the time of day specified by abstime . For POSIX threads, see pthread_cond_timedwait Syntax.

cond_timedwait Syntax

#include <thread.h>

int cond_timedwait(cond_t *cv, mutex_t *mp, timestruct_t abstime);

cond_timedwait() always returns with the mutex locked and owned by the calling thread, even when returning an error.

The cond_timedwait() function blocks until the condition is signaled or until the time of day specified by the last argument has passed. The timeout is specified as the time of day so the condition can be retested efficiently without recomputing the time-out value.

cond_timedwait Return Values

cond_timedwait() returns 0 if successful. When any of the following conditions is detected, cond_timedwait() fails and returns the corresponding value.


EFAULT

Description:

cv points to an illegal address.


ETIME

Description:

The time specified by abstime has expired.


EINVAL

Description:

abstime is invalid.

Waiting for a Time Interval

cond_reltimedwait(3C) is very similar to cond_timedwait(), except for the value for the third argument. cond_reltimedwait() takes a relative time interval value in its third argument rather than an absolute time of day value. For POSIX threads, see the pthread_cond_reltimedwait_np(3C) man page.

cond_reltimedwait() always returns with the mutex locked and owned by the calling thread even when returning an error. The cond_reltimedwait() function blocks until the condition is signaled or until the time interval specified by the last argument has elapsed.

cond_reltimedwait Syntax

#include <thread.h>

int cond_reltimedwait(cond_t *cv, mutex_t *mp,
    timestruct_t reltime);

cond_reltimedwait Return Values

cond_reltimedwait() returns 0 if successful. When any of the following conditions is detected, cond_reltimedwait() fails and returns the corresponding value.


EFAULT

Description:

cv points to an illegal address.


ETIME

Description:

The time specified by reltime has expired.

Unblock One Thread

Use cond_signal(3C) to unblock one thread that is blocked on the condition variable pointed to by cv . If no threads are blocked on the condition variable, cond_signal() has no effect.

cond_signal Syntax

#include <thread.h>

int cond_signal(cond_t *cv);

cond_signal Return Values

cond_signal() returns 0 if successful. When the following condition is detected, cond_signal() fails and returns the corresponding value.


EFAULT

Description:

cv points to an illegal address.

Unblock All Threads

Use cond_broadcast(3C) to unblock all threads that are blocked on the condition variable pointed to by cv. When no threads are blocked on the condition variable, then cond_broadcast() has no effect.

cond_broadcast Syntax

#include <thread.h>

int cond_broadcast(cond_t *cv);

cond_broadcast Return Values

cond_broadcast() returns 0 if successful. When the following condition is detected, cond_broadcast() fails and returns the corresponding value.


EFAULT

Description:

cv points to an illegal address.

Similar Synchronization Functions: Semaphores

Semaphore operations are the same in both the Solaris Operating Environment and the POSIX environment. The function name changed from sema_ in the Solaris Operating Environment to sem_ in pthreads. This section discusses the following topics:

Initialize a Semaphore

Use sema_init(3C) to initialize the semaphore variable pointed to by sp by count amount.

sema_init Syntax

#include <thread.h>

int sema_init(sema_t *sp, unsigned int count, int type,
    void *arg);

type can be one of the following values:

Multiple threads must not initialize the same semaphore simultaneously. A semaphore must not be reinitialized while other threads might be using the semaphore.

Semaphores With Intraprocess Scope

#include <thread.h>

sema_t sp;
int ret;
int count;
count = 4;

/* to be used within this process only */
ret = sema_init(&sp, count, USYNC_THREAD, 0); 

Semaphores With Interprocess Scope

#include <thread.h>

sema_t sp;
int ret;
int count;
count = 4;

/* to be used among all the processes */
ret = sema_init (&sp, count, USYNC_PROCESS, 0); 

sema_init Return Values

sema_init() returns 0 if successful. When any of the following conditions is detected, sema_init() fails and returns the corresponding value.


EINVAL

Description:

sp refers to an invalid semaphore.


EFAULT

Description:

Either sp or arg point to an illegal address.

Increment a Semaphore

Use sema_post(3C) to atomically increment the semaphore pointed to by sp. When any threads are blocked on the semaphore, one thread is unblocked.

sema_post Syntax

#include <thread.h>

int sema_post(sema_t *sp);

sema_post Return Values

sema_post() returns 0 if successful. When any of the following conditions is detected, sema_post() fails and returns the corresponding value.


EINVAL

Description:

sp refers to an invalid semaphore.


EFAULT

Description:

sp points to an illegal address.


EOVERFLOW

Description:

The semaphore value pointed to by sp exceeds SEM_VALUE_MAX.

Block on a Semaphore Count

Use sema_wait(3C) to block the calling thread until the count in the semaphore pointed to by sp becomes greater than zero. When the count becomes greater than zero, atomically decrement the count.

sema_wait Syntax

#include <thread.h>

int sema_wait(sema_t *sp);

sema_wait Return Values

sema_wait() returns 0 if successful. When any of the following conditions is detected, sema_wait() fails and returns the corresponding value.


EINVAL

Description:

sp refers to an invalid semaphore.


EINTR

Description:

The wait was interrupted by a signal.

Decrement a Semaphore Count

Use sema_trywait(3C) to atomically decrement the count in the semaphore pointed to by sp when the count is greater than zero. This function is a nonblocking version of sema_wait().

sema_trywait Syntax

#include <thread.h>

int sema_trywait(sema_t *sp);

sema_trywait Return Values

sema_trywait() returns 0 if successful. When any of the following conditions is detected, sema_trywait() fails and returns the corresponding value.


EINVAL

Description:

sp refers to an invalid semaphore.


EBUSY

Description:

The semaphore pointed to by sp has a zero count.

Destroy the Semaphore State

Use sema_destroy(3C) to destroy any state that is associated with the semaphore pointed to by sp. The space for storing the semaphore is not freed.

sema_destroy(3C) Syntax

#include <thread.h>

int sema_destroy(sema_t *sp);

sema_destroy(3C) Return Values

sema_destroy() returns 0 if successful. When the following condition is detected, sema_destroy() fails and returns the corresponding value.


EINVAL

Description:

sp refers to an invalid semaphore.

Synchronizing Across Process Boundaries

Each of the synchronization primitives can be set up to be used across process boundaries. This cross-boundary setup is done by ensuring that the synchronization variable is located in a shared memory segment and by calling the appropriate init routine with type set to USYNC_PROCESS.

If type is set to USYNC_PROCESS, then the operations on the synchronization variables work just as the variables do when type is USYNC_THREAD.

mutex_init(&m, USYNC_PROCESS, 0);
rwlock_init(&rw, USYNC_PROCESS, 0);
cond_init(&cv, USYNC_PROCESS, 0);
sema_init(&s, count, USYNC_PROCESS, 0);

Example of Producer and Consumer Problem

Example 6–2 shows the producer and consumer problem with the producer and consumer in separate processes. The main routine maps zero-filled memory that main shares with its child process, into its address space. Note that mutex_init() and cond_init() must be called because the type of the synchronization variables is USYNC_PROCESS.

A child process is created to run the consumer. The parent runs the producer.

This example also shows the drivers for the producer and consumer. The producer_driver reads characters from stdin and calls the producer. The consumer_driver gets characters by calling the consumer and writes them to stdout.

The data structure for Example 6–2 is the same as that used for the solution with condition variables. See Examples of Using Nested Locking With a Singly-Linked List .


Example 6–2 Producer and Consumer Problem Using USYNC_PROCESS

main() {
    int zfd;
    buffer_t *buffer;

    zfd = open(“/dev/zero”, O_RDWR);
    buffer = (buffer_t *)mmap(NULL, sizeof(buffer_t),
        PROT_READ|PROT_WRITE, MAP_SHARED, zfd, 0);
    buffer->occupied = buffer->nextin = buffer->nextout = 0;

    mutex_init(&buffer->lock, USYNC_PROCESS, 0);
    cond_init(&buffer->less, USYNC_PROCESS, 0);
    cond_init(&buffer->more, USYNC_PROCESS, 0);
    if (fork() == 0)
        consumer_driver(buffer);
    else
        producer_driver(buffer);
}

void producer_driver(buffer_t *b) {
    int item;

    while (1) {
        item = getchar();
        if (item == EOF) {
            producer(b, `\0');
            break;
        } else
            producer(b, (char)item);
    }
}

void consumer_driver(buffer_t *b) {
    char item;

    while (1) {
        if ((item = consumer(b)) == '\0')
            break;
        putchar(item);
    }
}

A child process is created to run the consumer. The parent runs the producer.

Special Issues for fork() and Solaris Threads

Prior to the Solaris 10 release, Solaris threads and POSIX threads defined the behavior of fork() differently. See Process Creation: exec and exit Issues for a thorough discussion of fork() issues.

Solaris libthread supported both fork() and fork1(). The fork() call has “fork-all” semantics. fork() duplicated everything in the process, including threads and LWPs, creating a true clone of the parent. The fork1() call created a clone that had only one thread. The process state and address space are duplicated, but only the calling thread was cloned.

POSIX libpthread supported only fork(), which has the same semantics as fork1() in Solaris threads.

Whether fork() has “fork-all” semantics or “fork-one” semantics was dependent on which library is used. Link with -lthread to assign “fork-all” semantics to fork(). Link with -lpthread to assign “fork-one” semantics to fork().

Effective with the Solaris 10 release, fork() has the same semantics in both Solaris threads and POSIX threads. More specifically, fork1() semantics replicate only the caller. A new function, forkall() , is provided for those applications that require replicate-all semantics.

See Compiling and Linking a Multithreaded Program for more details.