Rogue Wave banner
Previous fileTop of DocumentContentsIndexNext file

3.5 The Runnable Object Classes

As explained in Section 3.4.2, "Introducing the Runnable," the runnable family of classes includes the basic mechanisms used to create, control, and monitor the threads of execution within your application.

In a synchronous runnable, the thread that calls start() is the same thread that executes run(); the flow of control is simply passed internally from the start() member to the run() member. By the time the start() function returns, the runnable has completed (or at least attempted to perform) its specified task, as shown in Figure 5.

Figure 5 -- Synchronous runnable

In a threaded runnable, a call to start() results in the creation of a new thread of execution, as shown in Figure 6. Following creation, this new thread proceeds to execute the run() member, freeing the thread that called start() to move on to other things.

Figure 6 -- Threaded runnable

In this guide, any thread that is currently executing within a runnable's run() member is commonly referred to as being inside that runnable. The Threading package allows only one thread at a time to be inside, or active, within a runnable. All other threads are considered to exist outside the runnable.

3.5.1 Defining a Runnable's Task

In addition to being distinguished by their dispatching behavior, the runnable object can also be identified according to the mechanism used to define its "task" or "work." Some runnables accept a functor object for execution, while others accept and execute other runnables objects.

3.5.1.1 Functor-Based Runnables

A functor is an object used to encapsulate a function call. Each functor keeps a pointer to the function and copies of the argument values that are to be passed to the function. Invoking a functor produces a call to the function.

The Functor package functors are an ideal mechanism for defining the work to be performed by a runnable. Each functor instance is invoked using a common, inherited interface that remains the same regardless of the actual function signature or argument values.

A functor-based runnable redefines the run() member to invoke a functor instance stored within the runnable. With this capability, you do not have to sub-class or use other intrusive techniques to customize the execution behavior of a runnable. The functor-base runnables allow you to dynamically specify the functions you want to execute when a runnable is started.

The functor-based runnables include:

For more information on functor-based runnables, see Section 3.5.2, "Creating Functor-based Runnables." For more information on functors, see Chapter 8, "The Functor Package."

3.5.1.2 Runnable Servers

In addition to the functor-based runnable classes, the Threading package also has runnable classes that accept other runnable objects for execution. This type of runnable is called a runnable server.

A runnable server is a threaded runnable that accepts and queues any number of runnable objects and executes them within its own internal thread or threads. The runnable server classes are:

These classes are covered in detail in Section 3.6, "The Server Classes."

3.5.1.3 Guard Functors

You can also specify a guard functor for each runnable instance that you pass to a runnable server. This guard functor is invoked by the runnable server prior to dequeuing a runnable for execution. If the function called by the functor returns FALSE, the runnable is left on the internal runnable queue. Otherwise, if the function returns TRUE, the runnable is dequeued and dispatched.

The runnable server traverses its input queue, evaluating each runnable's guard until an executable runnable is found. If none of the currently queued runnables can execute, the server waits for another runnable to be queued, and when that happens, repeats the guard evaluation process.

Functors are covered in detail in Chapter 8, "The Functor Package."

3.5.2 Creating Functor-based Runnables

The technique used to create a runnable object depends on the particular type of runnable you want to create. The Threading package includes three different methods for constructing a functor-based runnable:

3.5.2.1 Global Template Functions

You can choose a global template function that relies on the compiler to extract the signature of a function you specify, allowing it to select, construct, and initialize an appropriate functor and runnable class instance. This style is illustrated in Example 4.

Example 4 -- Creating a functor-based runnable with a global template function

As explained in Section 3.4.3, "Runnable Make Functions," rwtMakeThreadFunction(), is one function in a family of template functions.


NOTE: Global template functions like rwtMakeThreadFunction() are included in the Threading package to simplify development. They are, however, convenience functions that might not be 100% portable.

To use global template functions, your compiler must be able to extract template argument types from a function signature. In addition, some C++ compilers cannot use reference types with templates. You might not be able to compile these functions or you might experience incorrect runtime behavior when using a C++ reference data type. Check the Threads.h++ Build Guide for the latest information on which compilers suffer from this restriction. However, even if you are using one of those compilers, all of the templatized convenience functions used to create functor-based runnables and functors can be replaced with macro invocations that are 100% portable, though not quite as simple to use. Also, you can still instantiate and construct the desired target classes directly.

The templatized make functions for runnables can accept a global, static, or member function with zero to three arguments and any return type. See the Threads.h++ Reference Guide for a complete list of these functions.

Each templatized make function is named to reflect the type of runnable object it creates and the handle type returned:

3.5.2.2 Make Macros

You can choose a macro that takes an explicit definition of a function signature and use it to select, construct, and initialize an appropriate functor and runnable class instance:

The runnable "make" macros are intended as compromise between the easy-to-use templatized make functions with their potential portability problems and the more complex coding required for explicit, low-level instantiation and construction.

The macros use names quite similar to the names used by the templatized make functions, except for the addition of a character sequence that is used to encode and identify the function type and number of arguments accepted by the macro. This sequence consists of the following elements:

The following macros show how this encoding relates to the macro parameter list.

Each of these macros returns a handle of the type indicated by the macro name. The first macro returns an RWRunnableFunction handle, while the second returns an RWThreadFunction handle.

See the Threads.h++ Reference Guide for a complete list of these macros.

3.5.2.3 Static Make Functions

You can choose to construct your own functor instance and pass it to one of the static make() functions in the runnable handle class, as shown in Example 5.

Example 5 -- Creating a functor-based runnable with a static make function

All of the runnable handle classes, except for RWRunnableSelf and RWThreadSelf, have static make() functions for constructing the corresponding runnable object.

The functor-based runnable classes each have a make() function that accepts a handle to a functor instance. If you use these functions, you need to construct your own functor instance, and pass a handle for that instance to the make function.

The threaded runnable classes have make() functions that can also take an RWThreadAttribute instance for use in controlling the attributes of any threads created by the runnable.

See the Threads.h++ Reference Guide for a complete list of these functions.

3.5.3 Runnable State

Each runnable object maintains two pieces of state information: a current execution state, and a current completion state. The following paragraphs explain the various operations that the Threading package includes for manipulating runnable objects and their state.


NOTE: Many of these operations rely on or make changes to the execution or completion state of a runnable. To help you better understand these relationships, study the state diagrams in Figure 7 and Figure 8 and use them as a reference as you read.

The current execution state is represented by the enumerated type RWExecutionState, which defines the following state values:

Figure 7 demonstrates the various execution state transitions.

Figure 7 -- Execution state transitions

The current completion state is represented by the enumerated type RWCompletionState, which defines the following state values:

Figure 8 demonstrates the various completion state transitions.

Figure 8 -- Completion state transitions

In addition to the execution and completion states, each runnable also captures any exceptions produced during execution. Any of these exceptions can be recovered by requesting the runnable to rethrow the exception.

3.5.4 Runnable Operations

As stated earlier, the runnable is the central mechanism for creating, controlling, and monitoring threads of execution. All runnable objects inherit a common set of operations and capabilities that support these activities. These operations are listed below and described in detail in the following sections:

3.5.5 Starting a Runnable

Starting a runnable initiates the execution of the runnable's task, as defined by its run() member. A start operation results in the synchronous or asynchronous execution of a runnable's defined task, depending on the type of runnable. Asynchronous runnables create new threads to complete their tasks.

You must start a runnable object before it can begin execution of any activity or task that it defines. All runnable objects are started by calling the runnable's start() function.

By starting a runnable, you begin a process that results in the eventual execution of that runnable's virtual run() member. Each runnable class redefines the run() member to supply its own class-specific behavior.

The functor-based runnable classes include a version of the run() function that invokes the functor object supplied by some client. Similarly, the runnable server classes redefine run() to dequeue runnable objects and execute them by calling their start() members.

Although all runnables are started in the same way, the underlying start processes differ. The differences stem from the way a particular runnable class dispatches its run() function. The synchronous runnable classes have a different start-up process compared to that of the asynchronous, or threaded, runnable classes.

In the descriptions that follow, the numbers shown in the figures indicate the transitions produced by various events or operations. Numbers that appear inside square-brackets [x] refer to transitions in the execution state diagram. Numbers that appear in side curly braces {x} refer to transitions in the completion state diagram.

3.5.5.1 Starting Synchronous Runnables

A synchronous runnable does not create its own thread to accomplish its tasks. It must use, or borrow, the thread that calls start() to execute the run() member. It is designed to pass control from the start() function to the internal run() function after performing any necessary initialization tasks.

Because run() is executed synchronously, start() cannot return control to its caller until run() returns control. This process is explained in the following steps and is illustrated in Figure 9.

  1. When first constructed, a runnable object is initialized with an execution state of RW_THR_INITIAL[1] and a completion state of RW_THR_PENDING{1}.

  2. The runnable briefly enters an execution state of RW_THR_STARTING[2] before entering its run() function, when the state is changed to RW_THR_RUNNING[3].

  3. Once the runnable successfully completes its activities and returns from its run() function, the state reverts back to RW_THR_INITIAL[4] and start() returns a completion state of RW_THR_NORMAL{2}.

  4. If an exception is thrown as a result of runnable activities and propagated back out of run(), the runnable enters the RW_THR_EXCEPTION[5] state before returning to the RW_THR_INITIAL[6] state and returning a completion state of RW_THR_FAILED{3}.

  5. Figure 9 -- Start operation for synchronous runnables - interaction and timing

You can start a synchronous runnable any number of times, but only one thread at a time can execute the encapsulated function within the runnable. If a second thread attempts to start a runnable that is currently being executed by another thread, the start() function produces an RWTHRThreadActive exception.

3.5.5.2 Checking the Completion State

You can use the completion state value returned from the start() function to determine whether or not the runnable ran to completion. The start() function returns the following completion states:

The code fragment in Example 6 shows how to check for completion states.

Example 6 -- Checking a runnable's completion state

3.5.5.3 Starting Threaded Runnables

An asynchronous, threaded runnable creates its own thread to accomplish its tasks. When you call RWRunnable::start(), the runnable launches a new thread to execute the run() member. The process is explained in the following steps and is illustrated in Figure 10.

  1. When first constructed, a threaded runnable object is initialized with an execution state of RW_THR_INITIAL[1] and a completion state of RW_THR_PENDING{1}.

  2. The runnable enters an execution state of RW_THR_STARTING[2] before creating the new thread. The starting thread requests and waits for the new thread to interrupt.

  3. The interrupt occurs before the new thread enters its run() member. This step insures that the thread doing the starting does not return from the start() function until the new thread completes any necessary initialization. Once the new thread is interrupted, the Threading package sets the initial scheduling policy and priority for the thread.

  4. Once the new thread begins execution, it attempts to service the interrupt request. This causes the execution state to change to RW_THR_INTERRUPTED[7].

  5. The start policy of the runnable is evaluated. Depending on the current policy setting, the new thread is either left in the interrupted state or released. In either case, the start() function returns control to the calling thread.

  6. a.

    If the threaded runnable has a start policy value of RW_THR_START_INTERRUPTED, the start() function returns without releasing the interrupt.

    b.

    If the threaded runnable has a start policy of RW_THR_START_RUNNING, the interrupt is released by the starting thread, the execution state is changed to RW_THR_RUNNING[9] state, and the start() function returns a completion state of RW_THR_PENDING to the calling thread. For more information about the start policy and other thread attributes, see Section 3.9, "Thread Attributes."


  7. Once a runnable's thread successfully completes its activities and returns from the run() function, the runnable's execution state is changed to RW_THR_INITIAL[4] and its completion state is set to RW_THR_NORMAL{2}.

  8. If an exception thrown from within the run() member is successfully caught within the runnable, the completion state changes to RW_THR_FAILED{3} and the execution state briefly changes to RW_THR_EXCEPTION[5] before changing back to the RW_THR_INITIAL[6] state.

  9. Figure 10 -- Start operation for threaded runnables - interaction and timing

3.5.5.4 Distinguishing Between Synchronous and Threaded Runnables

The completion state value returned from the start() function can be used to distinguish between a synchronous runnable and a threaded runnable, as shown in Example 7. The start() function on a synchronous runnable always returns a completion state of RW_THR_NORMAL, RW_THR_FAILED, or RW_THR_CANCELED. Threaded runnables always return RW_THR_PENDING.

Example 7 -- Using completion state to distinguish synchronous from threaded runnables

3.5.5.5 Restarting a Threaded Runnable

You can restart a threaded runnable, but only after the thread created by the previous call to start() has exited. Any attempt to call start() on a threaded runnable that already has an active thread results in an RWTHRThreadActive exception.

3.5.6 Joining a Runnable

The RWRunnable::join() function waits for a runnable to complete execution. You can join with both synchronous and threaded runnables. It is not necessary for the thread that starts a synchronous runnable to join with that runnable because the runnable has completed execution when it returns from start().

The join operation is implemented by the following functions:

A runnable can be joined by any number of threads and any number of times by the same thread.

Figure 11 shows the timing-sequence and state changes associated with the join operation.

Figure 11 -- Join operation - interaction and timing

3.5.6.1 Types of Join Functions

The Threading package has two types of join functions:

Several functions in the Threading package accept a timeout value. This value is meant to specify the amount of "wall-clock" time, in milliseconds, that a function waits before timing out.


NOTE: In some situations, functions that accept a timeout value actually measure duration by the amount of CPU time used, not by the elapsed wall-clock time.

This may result in longer timeout periods than are expected or intended. In these situations, the amount of delay is directly proportional to the percentage of available CPU time granted to the process.

The code segment in Example 8 illustrates both types of join functions.

Example 8 -- Joining runnables in two ways

3.5.6.2 Joining Unstarted or Inactive Runnables

If you join a runnable that has never been started, the join() function waits for the runnable to start and complete. If you join a runnable that is currently inactive, but has already been started previously, the join() function returns immediately. To wait for the next completion on a runnable that has already completed one or more executions, you need to delay until after the runnable has been restarted, as indicated by the RW_THR_STARTING execution state or by a completion state of RW_THR_PENDING.

3.5.7 Monitoring the Completion State of a Runnable

Each runnable maintains an internal completion state value that can be queried to determine whether a runnable has completed execution and what conditions led to the runnable's completion. Once you have started or joined with a runnable, you can use the RWRunnableHandle::getCompletionState() function to check the completion state of the runnable.

3.5.8 Catching and Rethrowing Exceptions

Exceptions produced while executing a runnable's task are captured by the runnable for later recovery. Any of these exceptions can be rethrown by any thread that has access to the runnable object. This operation is implemented by the following function:

A runnable catches any exception that propagates out of its run() member function. This includes any exceptions produced by functions invoked as functors within the runnable. When a runnable catches an exception, it:

  1. Makes an internal copy of the exception.

  2. Sets the execution state to RW_THR_EXCEPTION[5].

  3. Sets the completion state to RW_THR_FAILED{3}.

  4. Sets the execution state to RW_THR_INITIAL[6].

  5. Ceases execution by returning from start() or exiting the thread.

Any exception caught by the runnable can then be recovered by rethrowing the exception using the RWRunnable::raise() function. If no exception has occurred, this function returns without doing anything.

3.5.8.1 Testing After Successful Joins

You should test for an exception each time you successfully join with a runnable, as shown in Example 9.

Example 9 -- Testing for an exception after joining with a runnable

3.5.8.2 Types of Rethrown Exceptions

The type of exception that is rethrown by a runnable might not match the original exception class that was thrown or caught.

When designing code that you expect to execute inside a Threading package runnable, you might want to use the RWTHRxmsg class as a base class for your exceptions. An exception derived from this class can be caught as an RWTHRxmsg, copied for storage using a virtual clone() function, and later "reconstituted" using a virtual raise() function defined by the class. This store-and-forward capability allows a runnable to capture and rethrow an exception.

The Threading package includes a template called RWTTHRCompatibleException, which can convert an existing exception class into an RWTHRxmsg-compatible exception.

3.5.9 Interrupting a Runnable

A runnable can have its execution interrupted at the request of another thread. A runnable can also interrupt itself. An interrupted runnable must be released from an interrupt by another thread in order to resume execution. This operation is implemented by the following functions:

Figure 12 shows the timing-sequence and state changes associated with various interrupt operations.

3.5.9.1 Avoiding Deadlock

Interrupts are used to halt execution of a thread at predictable locations within your application code. Traditionally, the suspend and resume capability of a thread API would be used to interrupt the execution of a thread. But thread suspension is asynchronous and occurs regardless of the current activity of the thread. Suspending a thread that is currently holding a mutex can result in an unrecoverable deadlock situation. Even if your code does not explicitly use mutexes or other synchronization mechanisms, other libraries, such as a compiler's run-time library, can use these mechanisms to protect global static data.

To avoid the problems associated with thread suspension, the Threading package can synchronously interrupt a thread executing within a runnable. This is done through the interrupt mechanism, rather than suspend and resume.

Figure 12 -- Interrupt operations - interactions and timing

3.5.9.2 Interrupting a Runnable from Another Thread

To interrupt a runnable from another thread, you need to call the RWRunnable function, requestInterrupt(). This function increments an interrupt count maintained within the runnable instance and waits for the runnable to interrupt.

3.5.9.3 Completing the Interrupt Request

To complete the interrupt request, the thread executing inside the runnable must call the serviceInterrupt() function provided by the RWRunnableSelf handle class. This function blocks the calling thread as long as one or more outstanding interrupt requests exist.

Prior to blocking, the interrupted thread sets the execution state to RW_THR_INTERRUPTED[8]. This state change signals acknowledgment to all of the threads that requested an interrupt. The interrupted thread remains blocked inside the serviceInterrupt() routine until signaled that all acknowledged interrupt requests have been released. The serviceInterrupt() function returns TRUE if the caller was interrupted by the call and FALSE otherwise.

The RWRunnable::releaseInterrupt() function unblocks the thread in an interrupted runnable. Each time the release function is called, the internal interrupt count is decremented. When the count reaches zero, the releaseInterrupt() function restores the original execution state (usually RW_THR_RUNNING[9]), thereby signaling the blocked thread to resume execution. Any attempt to call releaseInterrupt() on a runnable that is not interrupted is ignored.

3.5.9.4 Types of requestInterrupt() Functions

The Threading package has two types of requestInterrupt() functions:

The requestInterrupt() functions return one of the following:

Several functions in the Threading package accept a timeout value. This value is meant to specify the amount of "wall-clock" time, in milliseconds, that a function waits before timing out.


NOTE: In some situations, functions that accept a timeout value actually measure duration by the amount of CPU time used, not by the elapsed wall-clock time.

This may result in longer timeout periods than are expected or intended. In these situations, the amount of delay is directly proportional to the percentage of available CPU time granted to the process.

3.5.9.5 Shutting Down a Continuous Process

An interrupt can be used to shutdown a continuous, iterative process by using the serviceInterrupt() routine to control the process iteration, as in Example 10.

Example 10 -- Using an interrupt to shut down a continuous process

To shutdown the iterative process, an external thread calls requestInterrupt() and follows that with a call to releaseInterrupt():

3.5.9.6 Rendezvous Synchronization

You can cause a runnable to interrupt itself by using the interrupt() method in the RWRunnableSelf class. This allows you to implement rendezvous or barrier synchronization. A rendezvous defines a meeting point between two threads. The thread that arrives at the meeting point first must wait for the other thread to arrive before continuing.

Example 11 shows how to use the interrupt() and requestInterrupt() calls to implement a simple rendezvous. The thread on one side of the rendezvous uses interrupt() to block itself while waiting for the other thread to arrive:

Example 11 -- Implementing a simple rendezvous between threads

The thread on the other side of the rendezvous uses requestInterrupt() to block while waiting for the other thread to arrive:

3.5.9.7 Interrupting Threads at Startup

The threads created by the threaded runnable classes can also be interrupted at startup by using an RWThreadAttribute instance to specify a start policy of RW_THR_START_INTERRUPTED. The releaseInterrupt() function must be used to release any thread interrupted in this manner.

3.5.10 Canceling a Runnable

Canceling a runnable is implemented by the following functions:

Cancellation is used to request a thread to exit a runnable. The cancellation operations are similar in function to the interrupt operations. Like the interrupt, cancellation is a synchronous process; a thread can only be canceled if it chooses to be.

3.5.10.1 Canceling a Runnable from Another Thread

To cancel a runnable from another thread, you need to call the RWRunnable function, requestCancellation(). This function sets a flag maintained within the runnable instance, and waits for the runnable to complete cancellation.

3.5.10.2 Completing a Cancellation Request

To complete the cancellation request, the thread executing inside the runnable must call the serviceCancellation() function provided by the RWRunnableSelf handle class. If there is an outstanding cancellation request, this function throws an RWCancellation exception object; otherwise the function simply returns to the caller. A thrown cancellation object notifies the current runnable that cancellation has started, causing the runnable's execution state to change to RW_THR_CANCELING[10].

At this point, the C++ exception processing begins unwinding the call-stack until an exception handler is found for the RWCancellation object. If the code that was executing inside the runnable does nothing to stop the unwind, the cancellation exception propagates all the way back out of the runnable's run() member and into exception-handling code inside the runnable class. If this happens, the cancellation process concludes, and the requesting thread is notified that cancellation has completed successfully.

When a runnable is canceled, its completion state is changed to RW_THR_CANCELED{4}, its execution state is reset back to the RW_THR_INITIAL[11], and an RWTHROperationCanceled exception is stored for retrieval by raise().

3.5.10.3 Aborting a Cancellation

The cancellation process can be aborted by catching the RWCancellation exception object before it can propagate back into the runnable. When the cancellation object is destroyed, it automatically notifies the current runnable that the cancellation process has been aborted. The abort notification causes the runnable to enter the RW_THR_ABORTING state and the RW_THR_RUNNING state. The RW_THR_ABORTING state is used to signal any waiting threads that the cancellation process was aborted.

3.5.10.4 Types of requestCancellation Functions

Threads.h++ has two types of requestCancellation() functions:

The requestCancellation() function returns one of the following:

Once a canceled runnable begins the cancellation process, it continues that process even if the original request for cancellation times out.

3.5.10.5 Undoing a Cancellation Request

A cancellation request cannot be undone; once a runnable is marked for cancellation it stays marked until it exits. An aborted cancellation is restarted the next time serviceCancellation() is called.

3.5.10.6 Using Synchronization Classes to Service Cancellation Requests

The Threads.h++ synchronization classes can automatically service cancellation requests before performing any operation that may result in blocking the calling thread.

To use this capability, a synchronization object must have its cancellation processing enabled. You can do this when the synchronization object is constructed by passing the appropriate RWCancellationState value to the constructor. The default cancellation state of a synchronization object is RW_CANCELLATION_DISABLED. You can override that default, however, by specifying RW_CANCELLATION_ENABLED when you construct the synchronization instance, as shown in Example 12.

Example 12 -- Constructing synchronization objects to automatically service cancellation requests

Except when using nested lockguards, you can also enable synchronization cancellation processing by using the setCancellation(RWCancellationState) member function provided by RWSynchObject class, the base class for all Threads.h++ synchronization mechanisms. Example 13 shows this approach.

Example 13 -- Setting synchronization objects to automatically service cancellation requests

Figure 13 shows the interaction and state changes associated with the cancellation process.

Figure 13 -- Cancel operations - interaction and timing

3.5.10.7 Designing Your Code

If you want to support cancellation in your application, you need to design your code to allow for the propagation of the RWCancellation object:

Throwing a new exception instance results in the destruction of the original RWCancellation instance, effectively aborting the cancellation process.

These coding guidelines are illustrated in the code fragments in Example 14.

Example 14 -- Supporting cancellation in an application

Each of these code fragments uses the global function rwRunnable() to retrieve a handle to the runnable that is the current source or active runnable of the calling thread. This handle is then used to call serviceCancellation(). This pair of statements may be replaced with the global function, rwServiceCancellation(), which combines these two operations into a single function.

Servicing cancellations can be very complicated when you are accessing thread-shared data that is protected by locks such as mutexes. If a runnable is canceled in the middle of acquiring and releasing locks on shared data, then the locks must also be released to prevent deadlock and the data must be set to a correct and consistent state.

For this reason, when using nested lockguards, avoid using the function RWMutexLock::setCancellation(RWCancellationState). Instead, before each attempt to acquire the mutex, use ::rwServiceCancellation() to service cancellation requests. To do otherwise may cause your attempts to acquire the mutex to fail, because the mutex is already canceled. This may cause your program to terminate, due to assertion errors. Example 15 demonstrates this concept.

Example 15 -- Serving cancellation requests with nested lockguards

3.5.11 Sleeping

A thread can use a sleep operation to yield execution to other threads. The sleep operation is an efficient way to block a thread for some specified minimum period of time. Threads can only put themselves to sleep; one thread cannot force another thread to sleep.

You can use two different functions to put a calling thread to sleep:

Both functions take an unsigned long value that specifies the number of milliseconds that the thread is to sleep. A thread is not guaranteed to resume execution once the sleep time period has elapsed. The thread merely becomes eligible for execution and might have to wait for processing resources to become available.

3.5.12 Yielding Execution

A thread can use a yield operation to free processing resources for the execution of other threads. The yield operation can be used in continuous or compute-bound operations to give other threads a chance to make progress.

You can use two different functions to yield execution:

3.5.12.1 Executing Under a Preemptive Scheduling Policy

When running under a preemptive thread scheduling policy, a thread does not give up its processor unless blocked by a synchronization mechanism or preempted by a higher priority thread. If a thread executing under this scheduling policy does not block, no other threads of the same or lesser priority get a chance to execute on the processor.

Yield can be used in this situation to give other threads of the same priority a chance to execute. A thread that yields is usually moved to the end of its current priority queue and does not get a chance to execute again until all other threads of the same priority have had a chance.

A yield operation does not force a thread to give up the processor if no other threads of the same priority are currently eligible for execution. A yield in this situation has no affect on the scheduling of the thread.

3.5.12.2 Yielding to Lower Priority Threads

To yield execution to threads of lower priority, it might be necessary to use the sleep() or rwSleep() functions.

3.5.13 Suspending and Resuming Execution

The thread in a threaded runnable can suspend its own execution or can be suspended by another thread. This operation is implemented by the following functions:

Suspension is an interruption in the execution of a thread. A suspended thread remains ineligible for execution until continued or resumed. Suspension is asynchronous-the suspended thread has no control over where and when it can be suspended. Only threaded runnables can be suspended. This capability is not included in the synchronous runnable classes.

When a threaded runnable is suspended, its execution state is set to RW_THR_SUSPENDED[18]. When the runnable is resumed, its execution state is restored to the state that existed before the suspension occurred[19].

Figure 14 shows the interaction and state changes associated with the suspension process.

Figure 14 -- Suspend and resume operations - interaction and timing

To suspend a thread created by a threaded runnable, use the suspend() function included by the RWThread handle class. A thread inside a threaded runnable can suspend itself by calling the RWThreadSelf::suspend() function.

The RWThread::resume() function is used to resume a suspended thread. A suspended thread cannot resume itself.

3.5.13.1 Using the suspend() and resume() Functions

The suspend() function can be called any number of times without an intervening call to resume(). The threaded runnable object maintains an internal count of the number of times the thread has been suspended. An equal number of calls to resume() must be made before the thread is allowed to continue. Both suspend() and resume() return the suspend-count that existed after the requested operation was performed.

The current suspend-count of a threaded runnable can be queried using the RWThread::getSuspendCount() function.

3.5.13.2 Deciding Whether Suspension Is Available

Suspension is not supported in all environments. The Threading package uses the macro RW_THR_CAN_SUSPEND_RESUME to indicate whether the suspend(), resume(), and getSuspendCount() operations are available. If this macro is not defined, the functions throw an RWTHROperationNotSupported exception. You can also determine whether suspension is supported at run-time by calling the canSuspendResume() function in the RWThread and RWThreadSelf classes.

3.5.14 Terminating Execution

When you terminate a thread, the target thread is immediately stopped, and its stack is reclaimed without unwinding. Any locks and resources held by the thread are abandoned. Use this operation only as a last resort because it may leave an application in an unknown and invalid state. Only threaded runnables can be terminated; the synchronous runnable classes do not have this capability.


NOTE: Solaris users: The Threading package uses a signal-based mechanism to terminate threads created under the native thread API. This mechanism may conflict with the signal usage in other parts of an application. Refer to the Platform User's Guide for more information.

The Threading package does not make direct use of signals or signal handling for termination or any other purpose in any of the other supported environments.

3.5.15 Monitoring the Execution State of a Runnable

Each runnable can exist in any one of a number of execution states. The current execution state of a runnable can be queried at any time.

Runnables allow other threads to wait for the runnable to enter a particular execution state. Callback methods can be registered for execution upon entry into states of interest.

This operation is implemented by the following functions:

3.5.15.1 Getting the Instantaneous Execution State of a Runnable

To determine the instantaneous execution state of a runnable, use the getExecutionState() function in the RWRunnableHandle class. This function returns one of the values defined by the RWExecutionState enumerated type, which are listed in Section 3.5.3, "Runnable State."

You should not use the getExecutionState() function to poll for runnable state changes. A polling loop using this function is unreliable because the runnable could conceivably make several state transitions between any two calls to this function. Also, the execution state retrieved might not reflect the current state of the runnable because the runnable might have shifted to another state by the time you can act on the value returned.

3.5.15.2 Monitoring Changes in Execution State

The Threading package has two mechanisms for monitoring execution state changes:

If you must reliably detect runnable state changes in your application, then use one of these two mechanisms. They are described in the following sections.

3.5.15.3 Using Wait Functions

The RWRunnable::wait() functions are used to wait for a runnable to enter any one of a number of execution states. The Threading package has two types of wait() functions:

Both functions take a mask value that specifies one or more execution states that trigger an end to the wait. The execution states of interest should be ORed together to form the mask value.

Because a wait can be satisfied by an entry into any one of several states specified by the mask value, each wait() function also returns the execution state value that triggered the end of the wait.

Using Server Classes. The wait functions are useful for detecting state changes in a runnable that has been passed to another thread for execution. The runnable server classes (discussed in Section 3.6, "The Server Classes,") accept runnable instances for execution within the server's thread or threads.

Example. The application code that creates a runnable and passes it to a server class instance might need to know when the runnable began executing. This is accomplished by using one of the wait functions, as shown in Example 16.

Example 16 -- Using a wait function to detect state changes in a runnable

3.5.15.4 Using Callbacks

Execution state callbacks are an asynchronous mechanism for reacting to execution state changes. Each time a runnable changes its execution state, it checks for and invokes any callbacks that have been registered for that particular execution state. Any callbacks associated with an execution state are invoked prior to releasing any threads waiting on that same execution state via the RWRunnable::wait() functions.

Callbacks are specified using functors. The functors used for callbacks are based on a different functor class family than those specified for execution within a runnable. These functors are passed the runnable and the current execution state when invoked. This allows the callback function to determine the source and state of each callback invocation. Any function that you create for use as an execution state callback can accept additional arguments and must accept the two values shown in Example 17 as its first two arguments.

Example 17 -- Creating a function for an execution state callback

To use your function as a callback, construct a compatible functor instance that calls the function when invoked, as shown in Example 18.

Example 18 -- Using a function as a callback

For more information on using and constructing functors, see the Threads.h++ Reference Guide and the Functor package.

3.5.15.5 Registering the Callback

To register a callback, use the RWRunnableHandle::addCallback() function. This function accepts:

Example 19 -- Registering a callback

As with the RWRunnable::wait() function, the execution state mask value can specify one or more execution states that trigger the callback. The execution states are ORed together to form the final mask value.

3.5.15.6 Changing the Execution State

The callbacks for most state changes are invoked and executed by the thread running inside a runnable, but some state change callbacks are invoked by threads that are manipulating the runnable to bring about a change in execution state.

The Threading package does not specify which thread executes a callback because this can change in future versions of the library. You have to explicitly test to see if the calling thread is associated with the source runnable using the threadId() and rwThreadId() functions, as shown in Example 20.

Example 20 -- Testing a calling thread's identity

You should take care to avoid making changes in the execution state of a runnable from within a callback function. The Threading package does not prohibit attempts to do so, but the results of such an operation can produce undesirable behavior or result in deadlock.

3.5.15.7 Using Template Functions and Macros

To simplify the creation of callback functors, the Threading package also has a family of template functions and macros that take care of instantiating the appropriate RWFunctor2 implementation class. By using the rwtMakeRunnableCallback() template function, the previous code fragments can be replaced with the code in Example 21.

Example 21 -- Creating a callback functor with a make function


NOTE: As with the other convenience functions in the Threading package, the template "make" functions might not be 100% portable.

If your compiler is unable to handle the use of these template functions, you need to use the equivalent macros or explicitly code the functor instantiation as done in the earlier example. For more information on using these callback functor make functions and macros, see the Threads.h++ Reference Guide.

3.5.15.8 Reusing Functors

The same functor can be registered as a callback on any number of runnables. The runnable handle passed as an argument at invocation can be used to identify the source of the callback. The same functor can also be registered more than once on a single runnable, but you should not have a reason for doing this.

3.5.15.9 Removing a Callback

The RWRunnableHandle::removeCallback() function is used to remove a callback from a runnable. To remove a callback, you must pass a handle to the same functor that was used to add the callback in the first place. The function removeCallback() removes all callbacks that were registered using the specified functor.

3.5.16 Execution Nesting

When an active runnable starts a second, synchronous runnable, the second runnable is executing or nested inside the first. Each runnable that is executing a nested runnable maintains an internal reference or link to that runnable. These links are traversed to find the innermost runnable currently executing under a thread.

Execution nesting is implemented by the following functions:

The rwThread() function can be used to get a handle to the threaded runnable that originated the calling thread. If the calling thread was not started by a Threading package runnable class, as is the case with the main thread, then the handle returned by this function is empty. Whenever the source of a thread is in question, you should use the isValid() function to test the handle returned by rwThread() before using it, as shown Example 22.

Example 22 -- Testing the source of a thread

3.5.16.1 Starting Synchronous Runnables

When a thread created by a threaded runnable starts a synchronous runnable, the thread eventually enters the run() member of that runnable. At that point, the thread is considered to be executing inside of the synchronous runnable as well.

In this situation, the synchronous runnable is nested inside of the threaded runnable. A nested runnable registers itself with the enclosing parent runnable for the purpose of supporting cancellation and other operations.

Starting a threaded runnable from within another runnable does not produce a nesting relationship because the threaded runnable launches its own thread of execution. No nesting relationship is formed when a synchronous runnable is started by a thread that does not originate from a Threading package runnable.

A handle to the nested runnable can be retrieved from an enclosing runnable by calling the getNestedRunnable() member. If a runnable does not have a nested runnable executing within it, an empty handle is return. This operation allows nested runnables to be traversed to find the innermost runnable.

3.5.16.2 Using the Nested Runnable Information

When a parent runnable is canceled, the cancellation request uses the nested runnable information to recursively propagate the request down to the innermost nested runnable. In the present version of the Threading package, cancellation is the only operation that propagates in this manner.

The nesting information can also be used to determine the current enclosing runnable for any thread that originated from a threaded runnable. Although it is possible to do this yourself by using the rwThread() and getNestedRunnable() functions, you can also use the Threading package function rwRunnable(). As was the case with the handle returned by rwThread(), the handle returned from rwRunnable() is empty if the current thread was not created by a Threading package runnable class.

3.5.17 Thread Identification

Runnable handles enable you to retrieve an identification value for each
thread that is created. This value is needed in many API thread-related functions. You might require the thread "id":

Thread identification is implemented by the following functions:

The Threading package encapsulates the underlying thread id using the RWThreadId class. Instances of this class can be used just as if they are instances of the underlying thread id type. That's because the RWThreadId class defines constructors and conversion operators that allow it to be used in this manner. For more information about this class, see the Threads.h++ Reference Guide.


NOTE: The RWThreadId class is in the Synchronization package.

You can get the thread id of the thread executing inside a runnable by calling the runnable's RWRunnableHandle::threadId() member. If the runnable does not have an active thread, then threadId() throws an RWTHRThreadNotActive exception.

You can use the Synchronization package rwThreadId() function to retrieve the id of the current or calling thread. Any thread can call this function, not just threads created by the library.


Previous fileTop of DocumentContentsIndexNext file

©Copyright 2000, Rogue Wave Software, Inc.
Contact Rogue Wave about documentation or support issues.