The server-side Ice run time by default creates a thread pool for you and automatically dispatches each incoming request in its own thread. As a result, you usually only need to worry about synchronization among threads to protect critical regions when you implement a server. However, you may wish to create threads of your own. For example, you might need a dedicated thread that responds to input from a user interface. And, if you have complex and long-running operations that can exploit parallelism, you might wish to use multiple threads for the implementation of that operation.
Ice provides a simple thread abstraction that permits you to write portable source code regardless of the native threading platform. This shields you from the native underlying thread APIs and guarantees uniform semantics regardless of your deployment platform.
On this page:
The basic thread abstraction in Ice is provided by two classes,
Thread (defined in
Thread class is an abstract base class with a pure virtual
run method. To create a thread, you must specialize the
Thread class and implement the
run method (which becomes the starting stack frame for the new thread). Note that you must not allow any exceptions to escape from
run. The Ice run time installs an exception handler that calls
run terminates with an exception.
The remaining member functions behave as follows:
start(size_t stBytes = 0)
start(size_t stBytes, int priority)
This member function starts a newly-created thread (that is, calls the
stBytesparameter specifies a stack size (in bytes) for the thread. The default value of zero creates the thread with a default stack size that is determined by the operating system.
You can also specify a priority for the thread. (If you do not supply a priority, the thread is created with the system default priority.) The priority value is system-dependent; on POSIX systems, the value must be a legal value for the
SCHED_RRreal-time scheduling policy. (
SCHED_RRrequires root privileges.) On Windows systems, the priority value is passed through to the Windows
setThreadPriorityfunction. Priority Inversion in C++ provides information about how you can deal with priority inversion.
The return value is a
ThreadControlobject for the new thread.
You can start a thread only once; calling
starton an already-started thread raises
If the calling thread passes an invalid priority or, on POSIX systems, does not have root privileges,
This member function returns a
ThreadControlobject for the thread on which it is invoked. Calling this method before calling
This method returns the underlying thread ID (
DWORDfor Windows and
pthread_tfor POSIX threads). This method is provided mainly for debugging purposes. Note also that
pthread_tis, strictly-speaking, an opaque type, so you should not make any assumptions about what you can do with a thread ID.
This method returns false before a thread's
startmethod has been called and after a thread's
runmethod has completed; otherwise, while the thread is still running, it returns true.
isAliveis useful to implement a non-blocking join:
These member functions compare the in-memory address of two threads. They are provided so you can use
Threadobjects in sorted STL containers.
IceUtil also defines the type
ThreadPtr. This is the usual reference-counted smart pointer to guarantee automatic clean-up: the
Thread destructor calls
delete this once its reference count drops to zero.
Implementing Threads in C++
To illustrate how to implement threads, consider the following code fragment:
This code fragment defines two classes,
WriterThread, that inherit from
IceUtil::Thread. Each class implements the pure virtual
run method it inherits from its base class. For this simple example, a writer thread places the numbers from 1 to 100 into an instance of the thread-safe
Queue class we defined in our discussion of monitors, and a reader thread retrieves 100 numbers from the queue and prints them to
Creating Threads in C++
To create a new thread, we simply instantiate the thread and call its
Note that we assign the return value from
new to a smart pointer of type
ThreadPtr. This ensures that we do not suffer a memory leak:
- When the thread is created, its reference count is set to zero.
- Prior to calling
run(which is called by the
startincrements the reference count of the thread to 1.
- For each
ThreadPtrfor the thread, the reference count of the thread is incremented by 1, and for each
ThreadPtrthat is destroyed, the reference count is decremented by 1.
startdecrements the reference count again and then checks its value: if the value is zero at this point, the
Threadobject deallocates itself by calling
delete this; if the value is non-zero at this point, there are other smart pointers that reference this
Threadobject and deletion happens when the last smart pointer goes out of scope.
Note that, for all this to work, you must allocate your
Thread objects on the heap — stack-allocated
Thread objects will result in deallocation errors:
This is wrong because the destructor of
t will eventually call
delete, which has undefined behavior for a stack-allocated object.
Similarly, you must use a
ThreadPtr for an allocated thread. Do not attempt to explicitly delete a thread:
This will result in a double deallocation of the thread because the thread's destructor will call
It is legal for a thread to call
start on itself from within its own constructor. However, if so, the thread must not be (very) short lived:
With this code, it is possible for
run to complete before the assignment to the smart pointer
ao completes; in that case,
start will call
delete this; before it returns and
ao ends up deleting an already-deleted object. However, note that this problem can arise only if
run is indeed very short-lived and moreover, the scheduler allows the newly-created thread to run to completion before the assignment of the return value of
operator new to
ao takes place. This is highly unlikely to happen — if you are concerned about this scenario, do not call
start from within a thread's own constructor. That way, the smart pointer is assigned first, and the thread started second, so the problem cannot arise.
start method returns an object of type
ThreadControl. The member functions of
ThreadControl behave as follows:
The default constructor returns a
ThreadControlobject that refers to the calling thread. This allows you to get a handle to the current (calling) thread even if you had not previously saved a handle to that thread. For example:
This example also explains why we have two classes,
ThreadControl: without a separate
ThreadControl, it would not be possible to obtain a handle to an arbitrary thread. (Note that this code works even if the calling thread was not created by the Ice run time; for example, you can create a
ThreadControlobject for a thread that was created by the operating system.)
The (implicit) copy constructor and assignment operator create a
ThreadControlobject that refers to the same underlying thread as the source
Note that the constructor is overloaded. For Windows, the signature is
For Unix, the signature is
These constructors allow you to create a
ThreadControlobject for the specified thread.
This method suspends the calling thread until the thread on which
joinis called has terminated. For example:
If the reader thread has finished by the time the creating thread calls
join, the call to
joinreturns immediately; otherwise, the creating thread is suspended until the reader thread terminates.
Note that the
joinmethod of a thread must be called from only one other thread, that is, only one thread can wait for another thread to terminate. Calling
joinon a thread from more than one other thread has undefined behavior.
joinon a thread that was previously joined with or calling
joinon a detached thread has undefined behavior.
You must join with each thread you create; failure to join with a thread has undefined behavior.
This method detaches a thread. Once a thread is detached, it cannot be joined with.
detachon an already detached thread, or calling
detachon a thread that was previously joined with has undefined behavior.
Note that, if you have detached a thread, you must ensure that the detached thread has terminated before your program leaves its
mainfunction. This means that, because detached threads cannot be joined with, they must have a life time that is shorter than that of the main thread.
This method suspends the calling thread for the amount of time specified by the
This method causes the calling thread to relinquish the CPU, allowing another thread to run.
These operators compare thread IDs. (Note that
operator<is not provided because it cannot be implemented portably.) These operators yield meaningful results only for threads that have not been detached or joined with.
C++ Thread Example
Following is a small example that uses the
Queue class we defined in our discussion of monitors. We create five writer and five reader threads. The writer threads each deposit 100 numbers into the queue, and the reader threads each retrieve 100 numbers and print them to
The code uses the
threads variable, of type
vector<IceUtil::ThreadControl>, to keep track of the created threads. The code creates five reader and five writer threads, storing the
ThreadControl object for each thread in the
threads vector. Once all the threads are created and running, the code joins with each thread before returning from
Note that you must not leave
main without first joining with the threads you have created: many threading libraries crash if you return from
main with other threads still running. (This is also the reason why you must not terminate a program without first calling
destroy implementation joins with all outstanding threads before it returns.)