To drive a Runnable object with a thread, you create a separate Thread object and hand a Runnable pointer to the Thread’s constructor. This performs the thread initialization and then calls the Runnable’s run( ) as an interruptible thread. By driving LiftOff with a Thread, the example below shows how any task can be run in the context of another thread:
//: C11:BasicThreads.cpp
// The most basic use of the Thread class.
//{L} ZThread
#include "LiftOff.h"
#include "zthread/Thread.h"
using namespace ZThread;
using namespace std;
int main() {
try {
Thread t(new LiftOff(10));
cout << "Waiting for LiftOff" << endl;
} catch(Synchronization_Exception& e) {
cerr << e.what() << endl;
}
} ///:~
Synchronization_Exception is part of the ZThread library and is the base class for all ZThread exceptions. It will be thrown if there is an error starting or using a thread.
A Thread constructor only needs a pointer to a Runnable object. It will perform the necessary initialization, call that Runnable’s run( ) member function to start the task, and then
You can easily add more threads to drive more tasks. Here, you can see how all the threads run in concert with one another:
//: C11:MoreBasicThreads.cpp
// Adding more threads.
//{L} ZThread
#include "LiftOff.h"
#include "zthread/Thread.h"
using namespace ZThread;
using namespace std;
int main() {
const int sz = 5;
try {
for(int i = 0; i < sz; i++)
Thread t(new LiftOff(10, i));
cout << "Waiting for LiftOff" << endl;
} catch(Synchronization_Exception& e) {
cerr << e.what() << endl;
}
} ///:~
The second argument for the LiftOff constructor identifies each task. When you run the program, you’ll see that the execution of the different tasks is mixed together as the threads are swapped in and out. This swapping is automatically controlled by the thread scheduler. If you have multiple processors on your machine, the thread scheduler will quietly distribute the threads among the processors.
The for loop can seem a little strange at first, because t is being created locally inside the for loop and then immediately goes out of scope and is therefore destroyed. This makes it appear that the thread itself might be immediately lost, but you can see from the output that the threads are indeed running to conclusion. When you create a Thread object, the associated thread is registered with the threading system, which keeps it alive. Even though the stack-based Thread object is lost, the thread itself lives on until its associated task completes. Although this may be counterintuitive from a C++ standpoint, the concept of threads is a departure from the norm: a thread creates a separate thread of execution that persists after the function call ends. This departure is reflected in the persistence of the underlying thread after the object vanishes.
Creating responsive user interfaces
As stated earlier, one of the motivations for using threading is to create a responsive user interface. Although we don’t cover
The following example reads lines from a file and prints them to the console,
//: C11:UnresponsiveUI.cpp
// Lack of threading produces an unresponsive UI.
//{L} ZThread
#include "zthread/Thread.h"
#include
#include
using namespace std;
using namespace ZThread;
int main() {
const int sz = 100; // Buffer size;
char buf[sz];
cout << "Press
ifstream file("UnresponsiveUI.cpp");
while(file.getline(buf, sz)) {
cout << buf << endl;
Thread::sleep(1000); // Time in milliseconds
}
// Read input from the console
cin.get();
cout << "Shutting down..." << endl;
} ///:~
To make the program responsive, you can execute a task that displays the file in a separate thread. The main thread can then watch for user input so the program becomes responsive:
//: C11:ResponsiveUI.cpp
// Threading for a responsive user interface.
//{L} ZThread
#include "zthread/Thread.h"
#include
#include