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.
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.
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.
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.
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:
RWRunnableFunction ----- A synchronous runnable class that uses a functor to execute a function whose return value, if any, can be ignored.
RWTRunnableIOUFunction ----- A synchronous class that uses a functor to execute a function whose return value is to be captured in an IOU. IOUs are a mechanism used to retrieve and wait for the result of an asynchronous operation. See Section 5.5 of the Interthread Communication Package chapter ("The IOU Classes") for more information.
RWThreadFunction ----- An asynchronous, threaded runnable that creates a new thread to invoke a functor that executes a function whose return value, if any, can be ignored.
RWTThreadIOUFunction ----- An asynchronous, threaded runnable that creates a new thread to invoke a functor that executes a function whose return value is to be captured in an IOU.
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."
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."
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."
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:
Global template functions
Make macros
Static make 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.
void hello(void); RWThreadFunction myThread = rwtMakeThreadFunction(hello);
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:
RWRunnableFunction ----- rwtMakeRunnableFunction()
RWTRunnableIOUFunction ----- rwtMakeRunnableIOUFunction()
RWThreadFunction ----- rwtMakeThreadFunction()
RWTThreadIOUFunction ----- rwtMakeThreadIOUFunction()
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:
RWThreadFunction myThread = rwtMakeThreadFunctionG(void,hello);
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:
A single letter indicating the function type:
G ----- The function is a global or static class member function.
M ----- The function is a non-static class member function.
An optional two letter code describing the number of arguments (if one of the following does not appear, then the function takes no arguments):
A1 ----- One argument.
A2 ----- Two arguments.
A3 ----- Three arguments.
The following macros show how this encoding relates to the macro parameter list.
rwtMakeRunnableFunctionG(R,function)
R - the function return type name (void, for example)
function - a pointer to a global function with the signature R (*)(void)
rwtMakeThreadFunctionMA1(Callee,callee,R,function,A1,a1)
Callee - the class name containing the member function
callee - a reference to a Callee class instance
R - the member function return type
function - a member function pointer with a signature R (Callee::*)(A1)
A1 - the type name of the function argument
a1 - the argument value that is to be passed to the function at invocation
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.
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.
RWFunctor0 myFunctor; myFunctor = rwtMakeFunctor0(NULL,hello); // Use functor "make" // function RWThreadFunction myThread; myThread = RWThreadFunction::make(myFunctor);
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.
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:
RW_THR_INITIAL ----- The initial state is used to indicate when a runnable is inactive. A runnable is created in the initial state and always returns to the initial state after it completes execution.
RW_THR_STARTING ----- A runnable temporarily enters the starting state after start() is called and remains in that state until the runnable is ready to execute its internal run() member.
RW_THR_RUNNING ----- The runnable is active and executing inside its run() member.
RW_THR_INTERRUPTED ----- The runnable is active, but has been interrupted, and is currently waiting to be released from the interrupt.
RW_THR_SUSPENDED ----- The threaded runnable is active, but its thread has been suspended, and is waiting to be resumed.
RW_THR_CANCELING ----- The runnable is active, but is currently attempting cancellation.
RW_THR_ABORTING ----- The runnable is active and has aborted an attempted cancellation.
RW_THR_SLEEPING ----- The runnable is active, but its thread is currently sleeping.
RW_THR_YIELDING ----- The runnable is active, but its thread is currently yielding execution.
RW_THR_SERVER_WAIT ----- The runnable server is active, but is currently waiting for another runnable to be enqueued for execution.
RW_THR_EXCEPTION ----- The runnable is exiting because an exception was produced.
RW_THR_TERMINATING ----- The runnable is being terminated by another thread.
Figure 7 demonstrates the various execution state transitions.
The current completion state is represented by the enumerated type RWCompletionState, which defines the following state values:
RW_THR_PENDING ----- The runnable has not been started since it was created, or the runnable is currently active. The completion state is reset to RW_THR_PENDING each time start() is called.
RW_THR_NORMAL ----- The runnable completed execution normally.
RW_THR_FAILED ----- The runnable exited with an exception.
RW_THR_CANCELED ----- The runnable was canceled.
RW_THR_TERMINATED ----- The thread executing the runnable was terminated.
Figure 8 demonstrates the various 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.
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:
Section 3.5.7, "Monitoring the Completion State of a Runnable."
Section 3.5.15, "Monitoring the Execution State of 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.
RWCompletionState RWRunnable::start(void)
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.
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.
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}.
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].
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}.
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}.
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.
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:
RW_THR_NORMAL if the runnable ran to completion.
RW_THR_FAILED if an exception was produced.
RW_THR_CANCELED if the runnable was canceled by another thread.
The code fragment in Example 6 shows how to check for completion states.
switch(runnable.start()) { case RW_THR_NORMAL: // The runnable ran to completion ... break; case RW_THR_FAILED: // The runnable exited with an exception ... break; case RW_THR_CANCELED: // The runnable was canceled during execution ... break; }
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.
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}.
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.
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.
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].
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.
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." |
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}.
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.
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.
void executeRunnable(RWRunnable& runnable) { RWCompletionState result; if (RW_THR_PENDING == (result = runnable.start())) { // Threaded Runnable ... } else { // Synchronous 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.
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:
void
RWRunnable::join(void)
RWWaitStatus
RWRunnable::join(unsigned long milliseconds)
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.
The Threading package has two types of join functions:
One waits indefinitely for the runnable to complete execution.
One accepts a time-limit for the wait.
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.
// Create a threaded runnable RWThreadFunction myThread = ... // Start the runnable myThread.start(); // Wait 1000 msecs (1 second) for the runnable's thread to exit. if (RW_THR_TIMEOUT == myThread.join(1000)) { // The runnable did not finish within one second! // Now wait as long as it takes... myThread.join(); }
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.
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.
The completion state for a runnable that has not yet been started or not yet completed is RW_THR_PENDING.
If the runnable completion state is RW_THR_FAILED, an exception was thrown within the runnable. You can rethrow the exception by calling the RWRunnable::raise() member. The raise function can be called at any time, and does nothing if no exception has been captured by the runnable.
If the completion state is RW_THR_CANCELED, the runnable was successfully canceled, either by another thread, or of its own accord.
A completion state of RW_THR_TERMINATED indicates that the runnable's active thread was terminated.
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:
void
RWRunnable::raise(void) const
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:
Makes an internal copy of the exception.
Sets the execution state to RW_THR_EXCEPTION[5].
Sets the completion state to RW_THR_FAILED{3}.
Sets the execution state to RW_THR_INITIAL[6].
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.
You should test for an exception each time you successfully join with a runnable, as shown in Example 9.
RWThreadFunction myThread = ...; try { myThread.start(); // Start the runnable myThread.join(); // Join the runnable myThread.raise();// Rethrow any exception that was caught! } catch(RWTHRxmsg& msg) { cout << "Exception! " << msg.why() << endl; }
The type of exception that is rethrown by a runnable might not match the original exception class that was thrown or caught.
If the exception is based on the RWTHRxmsg class, then the exception rethrown by raise() is of the same class as the one captured.
Exceptions based on the RWxmsg (or xmsg) class are rethrown as an RWxmsg with the same message value as the captured exception.
Any other type of exception is rethrown as an RWTHRxmsg with a generic message stating that the captured exception was of an unrecognized type.
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.
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:
RWWaitStatus
RWRunnable::requestInterrupt(void)
RWWaitStatus
RWRunnable::requestInterrupt(unsigned long milliseconds)
void
RWRunnable::releaseInterrupt(void)
void
RWRunnableSelf::interrupt(void)
RWBoolean
RWRunnableHandle::isInterruptRequested(void) const
RWBoolean
RWRunnableSelf::serviceInterrupt(void)
Figure 12 shows the timing-sequence and state changes associated with various interrupt operations.
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.
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.
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.
The Threading package has two types of requestInterrupt() functions:
One waits indefinitely for the runnable to either interrupt or exit.
One accepts a time-limit for the wait.
The requestInterrupt() functions return one of the following:
RW_THR_ABORTED if the runnable was not active or exited during the request.
RW_THR_ACQUIRED if the runnable was successfully interrupted.
RW_THR_TIMEOUT if the request was time-limited and the runnable failed to interrupt within the allotted time.
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.
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.
. . . RWRunnableSelf self = rwRunnable(); // Perform iterative operation until interrupted... while(!self.serviceInterrupt()) { // Perform operation } // Operation interrupted! . . .
To shutdown the iterative process, an external thread calls requestInterrupt() and follows that with a call to releaseInterrupt():
. . . // "theThread" is the iterative process // Request shutdown! if (RW_THR_ABORTED == theThread.requestInterrupt()) { // Oops! The process isn't running anymore! } else { theThread.releaseInterrupt(); } . .
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:
RWRunnableSelf self = rwRunnable(); while(/*condition*/) { ... // Do something useful... ... // Rendezvous with the other thread // Wait here until the other thread releases the interrupt! self.interrupt(); }
The thread on the other side of the rendezvous uses requestInterrupt() to block while waiting for the other thread to arrive:
// "other" points to the runnable containing the other thread while(/*condition*/) { ... // Do something useful... ... // Rendezvous with the other thread // Wait here until the other thread interrupts! if (RW_THR_ACQUIRED == other.requestInterrupt()) // Must release twice because the thread was // interrupted twice { other.releaseInterrupt(); other.releaseInterrupt(); } }
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.
Canceling a runnable is implemented by the following functions:
RWWaitStatus
RWRunnable::requestCancellation(void)
RWWaitStatus
RWRunnable::requestCancellation(unsigned long milliseconds)
void
RWRunnableSelf::serviceCancellation(void)
void
rwServiceCancellation();
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.
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.
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().
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.
Threads.h++ has two types of requestCancellation() functions:
One waits indefinitely for the runnable to either cancel or exit.
One accepts a time-limit for the wait.
The requestCancellation() function returns one of the following:
RW_THR_COMPLETED if the runnable was successfully canceled, exited, or was not active in the first place.
RW_THR_ABORTED if the cancellation process was started, but the RWCancellation exception object was not allowed to propagate back to the runnable class.
RW_THR_TIMEOUT if the request was time-limited and the runnable failed to complete cancellation within the allotted time.
Once a canceled runnable begins the cancellation process, it continues that process even if the original request for cancellation times out.
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.
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.
RWMutexLock mutex(RW_CANCELLATION_ENABLED); try { mutex.acquire(); // May throw cancellation! } catch(RWCancellation&) { // Do something about it! }
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.
RWMutexLock mutex; // Defaults to disabled! mutex.setCancellation(RW_CANCELLATION_ENABLED); try { mutex.acquire(); // May throw cancellation! } catch(RWCancellation&) { // Do something about it! }
Figure 13 shows the interaction and state changes associated with the cancellation process.
If you want to support cancellation in your application, you need to design your code to allow for the propagation of the RWCancellation object:
Exception specifications for functions, if used, must include the RWCancellation or RWTHRxmsg exceptions.
Your catch(...) blocks must rethrow the original exception.
Any block that explicitly catches an RWCancellation or RWTHRxmsg exception must rethrow the original exception, or rethrow the instance that was caught.
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.
void function1() throw(RWCancellation) { RWRunnableSelf self = rwRunnable(); try { self.serviceCancellation(); } catch(RWCancellation&) { // Cancellation occurred! throw; // Rethrow the original instance } } void function2() throw(RWTHRxmsg) { RWRunnableSelf self = rwRunnable(); try { self.serviceCancellation(); } catch(RWTHRxmsg& msg) { // Catch by reference! // An RWTHRxmsg exception occurred! throw msg; // Rethrow the instance caught } } void function3() // No spec! { RWRunnableSelf self = rwRunnable(); try { self.serviceCancellation(); } catch(...) { // Catches anything // Some exception occurred! throw; // Rethrow the original instance } }
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.
#include <rw/thread/RWRunnableSelf.h> #include <rw/thread/RWThread.h> #include <rw/thread/RWCancellation.h> #include <rw/thread/rwtMakeThreadFunction.h> #include <rw/sync/RWMutexLock.h> #include <iostream.h> void func1(void) { RWMutexLock _mutex; ::rwServiceCancellation(); // Don't use // _mutex.setCancellation(); // Instead, service cancellations // before attempting to acquire the // mutex. RWLockGuard<RWMutexLock> lock(_mutex); // Acquire the mutex. rwSleep(1); { RWUnlockGuard<RWMutexLock> unlock(_mutex);// Release the mutex. cout << "going to sleep..." << endl; rwSleep(1500); cout << "waking up..." << endl; ::rwServiceCancellation(); // Service cancellations before you // attempt to acquire the mutex. } // Acquire the mutex. cout << "Everything was successful" << endl; } // Release the mutex. void main(void) { RWThread thr1 = rwtMakeThreadFunctionG(void, func1); thr1.start(); rwSleep(100); cout << "requesting cancellation..." << endl; thr1.requestCancellation(); cout << "cancellation request returned." << endl; }
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:
void
RWRunnableSelf::sleep(unsigned long milliseconds)
The RWRunnableSelf::sleep() function can be called by a thread running inside of a runnable object. This function sets the runnable execution state to RW_THR_SLEEPING[14] and then enters a blocking wait until the specified period of time has passed. Once the time-period has elapsed, the thread is unblocked and becomes eligible for execution. The runnable is returned to the RW_THR_RUNNING[15] once the thread begins executing again.
void
rwSleep(unsigned long milliseconds)
The global rwSleep() function can be called by any thread, regardless of whether or not it originated or is executing within a runnable object. This function is identical to the sleep() function except that it causes no changes in the execution state of a runnable.
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.
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:
void
RWRunnableSelf::yield(void)
The RWRunnableSelf::yield() function can be called by a thread running inside of a runnable object. This function sets the runnable execution state to RW_THR_YIELDING[16] before yielding execution. The execution state is reset to RW_THR_RUNNING[17] once the thread begins executing again.
void
rwYield(void)
The global rwYield() function can be called by any thread, regardless of whether or not it originated or is executing within a runnable object. This function is identical to the yield() function except that it causes no changes in the execution state of a runnable.
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.
To yield execution to threads of lower priority, it might be necessary to use the sleep() or rwSleep() functions.
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:
RWBoolean
RWThread::canSuspendResume(void) const
RWBoolean
RWThreadSelf::canSuspendResume(void) const
unsigned
RWThread::suspend()
void
RWThreadSelf::suspend()
unsigned
RWThread::getSuspendCount() const
unsigned
RWThread::resume()
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].
Any attempt to suspend an inactive, threaded runnable results in an RWTHRThreadNotActive exception.
Any attempt to resume a threaded runnable that is not suspended results in an RWTHRThreadActive exception.
Figure 14 shows the interaction and state changes associated with the suspension process.
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.
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.
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.
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.
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:
RWExecutionState
RWRunnableHandle::getExecutionState(void) const
RWExecutionState
RWRunnable::getExecutionState(void) const
RWExecutionState
RWRunnable::wait(unsigned long stateMask)
RWExecutionState
RWRunnable::wait(unsigned long stateMask
RWExecutionState* state,
unsigned long milliseconds)
void
RWRunnableHandle::addCallback(
const RWFunctor2<const RWRunnable&,
RWExecutionState>& functor,
unsigned long stateMask, RWCallbackScope scope)
void
RWRunnableHandle::removeCallback(
const RWFunctor2<const RWRunnable&,
RWExecutionState>& functor)
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.
The Threading package has two mechanisms for monitoring execution state changes:
A pair of runnable wait functions that you can use to make the calling thread wait until the runnable enters an execution state of interest.
A callback mechanism that allows you to register one or more functors as callbacks to be invoked when the corresponding runnable enters a targeted execution state.
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.
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:
One waits an indefinite amount of time for the runnable to enter a targeted execution state.
One accepts a time-limit value expressed as an unsigned long number of milliseconds.
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.
#include <rw/thread/rwRunnable.h> #include <rw/thread/rwtMakeRunnableFunction.h> #include <rw/thread/RWRunnableSelf.h> #include <rw/thread/RWServerPool.h> #include <rw/rstream.h> void hello() { RWRunnableSelf self = rwRunnable(); do { cout << "Hello World!" << endl; } while(!self.serviceInterrupt()); } void main() { // Create and start a server pool instance RWServerPool server = RWServerPool::make(3); server.start(); // Create and enqueue a runnable that says hello! RWRunnable runnable = rwtMakeRunnableFunctionG(void,hello); server.enqueue(runnable); // Wait for the runnable to start executing runnable.wait(RW_THR_RUNNING); // OK, that's enough of that! if (RW_THR_ACQUIRED == runnable.requestInterrupt()) runnable.releaseInterrupt(); // Shutdown the server pool server.stop(); server.join(); }
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.
void myCallback(const RWRunnable& runnable, RWExecutionState state) { // Do something useful }
To use your function as a callback, construct a compatible functor instance that calls the function when invoked, as shown in Example 18.
RWTFunctor2<const RWRunnable&,RWExecutionState> myFunctor; myFunctor = rwtMakeFunctor2G(const RWRunnable&, RWExecutionState, void, myCallback, const RWRunnable&, RWExecutionState);
For more information on using and constructing functors, see the Threads.h++ Reference Guide and the Functor package.
To register a callback, use the RWRunnableHandle::addCallback() function. This function accepts:
An RWTFunctor2 instance.
An execution state mask value.
An RWCallbackScope enumerated value that tells the runnable whether the callback should be invoked just once and deleted (RW_CALL_ONCE), or invoked as many times as the execute state requires to satisfy the mask (RW_CALL_REPEATEDLY).
RWRunnable runnable = ...; // Register callback that will be invoked each time the // the runnable is started or completes execution... runnable.addCallback(myFunctor, RW_THR_INITIAL | RW_THR_RUNNING, RW_CALL_REPEATEDLY);
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.
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.
void myCallback(const RWRunnable& runnable, RWExecutionState state) { // Are we executing in the runnable's active thread? if (runnable.threadId() == rwThreadId()) // This is the runnable's thread! else // This is some other thread! }
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.
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.
runnable.addCallback(rwtMakeRunnableCallback(myCallback), RW_THR_INITIAL | RW_THR_RUNNING, RW_CALL_REPEATEDLY);
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.
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.
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.
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:
RWRunnable
RWRunnableSelf::getNestedRunnable(void) const
RWRunnableSelf
RWRunnable::getNestedRunnable(void) const
RWRunnableSelf
rwRunnable(void)
RWThreadSelf
rwThread(void)
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.
RWThreadSelf thread = rwThread(); if (thread.isValid()) { // OK, this thread was created by a runnable }
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.
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.
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":
When you need to directly call the underlying API.
When doing your own thread identification within both your program logic and any debug or status output that you produce.
Thread identification is implemented by the following functions:
RWThreadId
rwThreadId()
RWThreadId
RWRunnableHandle::threadId()
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.
©Copyright 2000, Rogue Wave Software, Inc.
Contact Rogue Wave about documentation or support issues.