One module provided by the NASD API is the threads module. This presents interfaces for creating and manipulating threads, along with mutex and condition variables. Describing proper techniques for programming with threads, mutexes, and conditions is outside the scope of this document. For further information, the reader is recommended to Birrell's " An Introduction to Programming with Threads."
To use the NASD threads interface, a file must include <nasd/nasd_threadstuff.h>
.
To initialize the threads module, call nasd_threads_init()
. Like most
module initialization functions, this requires no arguments. When the module is
no longer needed, call nasd_threads_shutdown()
. Note that this
shutdown function should only be called after all related resources, such
as mutexes, conditions, and thread groups,
have been destroyed, and all threads created by this module have exited.
Threads have type nasd_thread_t
. A thread is created by
specifying a function to run which is that thread, and an argument
to pass to this function. This function must be of type void
,
and the argument of type nasd_threadarg_t
, which is
guaranteed to be large enough to hold a pointer. To cease execution,
this function must call NASD_THREAD_KILL_SELF()
, which is the equivalent
of returning from this void
function. It is not
acceptable to simply return from this function. It is also not acceptable
to call NASD_THREAD_KILL_SELF()
from any function other than
that which was directly specified when the thread was created.
The function which creates a thread is:
nasd_threadfunc_t
is defined as
(That is, a void
function taking a nasd_threadarg_t
as a
single argument.)
When the thread creation function returns, the thread has already been created, and is eligible for scheduling immediately. Indeed, it may be scheduled even before the calling thread receives the return value of the thread creation function, or it may not be dispatched until some later time. To coordinate thread start-up, you may wish to use a thread group.
Additional attributes may be specified for a newly created thread.
Thread attributes are represented by the opaque type nasd_threadattr_t
.
A single nasd_threadattr_t
may be used to create any number
of threads with the same attributes. A nasd_threadattr_t
may
also be destroyed after it has been used to create a thread, before that
thread has completed execution. Functions which manipulate thread attribute
representations are:
NASD_OP_NOT_SUPPORTED
for these operations. If that value
is returned by a get or set operation, it is guaranteed that the
contents of the attribute remain unchanged, and the attribute continues
to be a valid argument of nasd_thread_create_attr()
.
Sample code which creates threads exists in the section describing thread groups.
NASD_DECLARE_MUTEX()
are not
followed by a semicolon, as one might normally expect. Two other forms of
NASD_DECLARE_MUTEX()
exist, they are:static
keyword. That is, it may not be accessed directly by code in other files,
but may only be passed by reference. The latter provides a declaration
with the semantics of the C extern
keyword- no storage is
reserved for this symbol, but it may be legally accessed as a mutex, and
the linker will look for the storage to be declared in another object. It
is legal to NASD_DECLARE_EXTERN_MUTEX()
a mutex, and later
declare it locally with NASD_DECLARE_MUTEX()
. The results of
mixing NASD_DECLARE_EXTERN_MUTEX()
and
NASD_DECLARE_STATIC_MUTEX()
are undefined, however.
To create a mutex, call nasd_mutex_init()
. This takes a pointer
to a mutex as a single argument, and returns a nasd_status_t
indicating whether or not the initialization was successful. To destroy
a mutex, and free associated resources, call nasd_mutex_destroy()
with a pointer to the mutex. This also returns a nasd_status_t
indicating success or failure. (Failure usually indicates that either the
pointer was not to a valid mutex, or some memory corruption has occurred.)
Two interfaces are provided to lock mutexes: NASD_LOCK_MUTEX()
and NASD_TRY_LOCK_MUTEX()
. The former will acquire the mutex,
blocking if and only if necessary. The latter will never block, but may
fail to acquire the mutex. NASD_TRY_LOCK_MUTEX()
will
evaluate to zero (0
). In the case where NASD_TRY_LOCK_MUTEX()
fails (evaluates to zero), the state of the mutex remains completely unchanged.
In the case where NASD_TRY_LOCK_MUTEX()
evaluates non-zero,
it is as if NASD_LOCK_MUTEX()
just completed. To release a mutex
acquired with either NASD_LOCK_MUTEX()
or NASD_TRY_LOCK_MUTEX()
,
use NASD_UNLOCK_MUTEX()
. All three of these macros are invoked
with the mutex itself, not a reference.
Here is a sample program which illustrates the use of mutexes:
nasd_cond_init()
and nasd_cond_destroy()
,
each of which takes a pointer to a condition variable as its sole
argument, and returns a nasd_status_t
indicating the
result of this operation.
A thread may block on a condition by using the NASD_WAIT_COND()
interface. This takes two arguments: a condition variable and a mutex.
Both must be validly initialized, and the caller must hold the mutex.
NASD_WAIT_COND()
will atomically release the mutex and
wait for an event to be indicated on the condition variable. When the
condition is raised, NASD_WAIT_COND()
will reacquire the
mutex automatically before returning. A call to NASD_WAIT_COND()
would look something like:
There are two operations for raising a condition (signalling an event):
A readers/writers lock is declared with:
To take a readers/writers lock, call either
Important: although mutexes and readers/writers locks are in many
ways similar, it is never legal to pass a readers/writers lock as an argument
to an operation which expects a mutex, or vice-versa.
The NASD threads module is built atop a porting layer, below which
exists platform- and system-specific code. For that reason, those operations
which return type
No guarantee of wakeup ordering is made for threads waiting for
locks or conditions.
NASD_SIGNAL_COND()
and NASD_BROADCAST_COND()
. Each
takes as its sole argument a validly-initialized condition. For
instance, NASD_BROADCAST_COND(cond_name)
could be used
to awaken the caller of NASD_WAIT_COND
in the above example.
There is no requirement that the caller of NASD_BROADCAST_COND()
(or NASD_SIGNAL_COND()
) hold or not hold any mutexes associated
with any calls to NASD_WAIT_COND()
made on the condition
variable. NASD_SIGNAL_COND()
is guaranteed to awaken at least one
current caller of NASD_WAIT_COND()
(on the indicated condition variable),
if there are any. NASD_BROADCAST_COND() is guaranteed to awaken all
current callers of
NASD_WAIT_COND()
on the indicated condition variable.
A current caller of NASD_WAIT_COND()
is a calling thread which
is blocked in a call to NASD_WAIT_COND()
at the time
NASD_SIGNAL_COND()
or NASD_BROADCAST_COND()
is called by
another thread.
Readers/writers locks
In addition to mutexes, NASD also provides readers/writers locks.
These locks provide more complex semantics
than mutexes. At any given time, an arbitrary number of threads may hold
"read" locks, or exactly one thread may hold a "write" lock. This is intended
to provide protection of state shared between many threads, and allow many
threads to examine the state without modifying it concurrently, but allow
a single thread to modify the state without confusing these readers.
nasd_rwlock_init()
with a pointer to the lock to be initialized. To destroy it, call
nasd_rwlock_destroy()
with a pointer to the lock to
be destroyed. Both of these functions return type nasd_status_t
.
NASD_LOCK_READ()
or NASD_LOCK_WRITE()
, passing a readers/writers lock as
the sole argument. After NASD_LOCK_READ()
completes, there
may be any number of other threads also holding "read" locks on this
readers/writers lock,
but there will be no threads holding write locks. After NASD_LOCK_WRITE()
completes, there will be no other threads holding any locks on this
readers/writers lock. To release a lock after calling NASD_LOCK_READ()
,
call NASD_UNLOCK_READ()
. To release a lock after calling
NASD_LOCK_WRITE()
, call NASD_UNLOCK_WRITE()
.
Notes on the threads API
The NASD threads module does not guarantee that there will not be spurious
wakeups of callers of NASD_WAIT_COND()
. Callers should always
reverify that the event which they were awaiting has actually occurred.
Note also that NASD_SIGNAL_COND()
may generate extraneous
wakeups of multiple threads.
nasd_status_t
may return any code
which the particular port of the NASD code you are using deems fit.
This may include error codes which are specific to the system or
platform you are running on. However, these error codes should always be
translatable to meaningful text with nasd_error_string()
.
Modules
Thread groups
NASD Programmer's Documentation