using namespace ZThread;
using namespace std;
class DisplayTask : public Runnable {
ifstream in;
static const int sz = 100;
char buf[sz];
bool quitFlag;
public:
DisplayTask(const string& file) : quitFlag(false) {
in.open(file.c_str());
}
~DisplayTask() { in.close(); }
void run() {
while(in.getline(buf, sz) && !quitFlag) {
cout << buf << endl;
Thread::sleep(1000);
}
}
void stop() { quitFlag = true; }
};
int main() {
try {
cout << "Press
DisplayTask* dt = new DisplayTask("ResponsiveUI.cpp");
Thread t(dt);
cin.get();
dt->stop();
} catch(Synchronization_Exception& e) {
cerr << e.what() << endl;
}
cout << "Shutting down..." << endl;
} ///:~
Now the main thread can respond immediately when you press
This example also shows the need for communication between tasks—the task in the main thread needs to tell the DisplayTask to shut down. Of course, since we have a pointer to the DisplayTask, you might think of just calling delete on that pointer to kill the task, but this produces unreliable programs. The problem is that the task could be in the middle of something important when you destroy it, and so you are likely to put the program in an unstable state. Here, the task itself decides when it’s safe to shut down. The easiest way to do this is by simply notifying the task that you’d like it to stop by setting a Boolean flag. When the task gets to a stable point it can check that flag and do whatever is necessary to clean up before returning from run( ). When the task returns from run( ), the Thread knows that the task has completed.
Although this program is simple enough that it should not have any problems, there are some small flaws regarding inter-task communication. This is an important topic that will be covered later in this chapter.
Simplifying with Executors
You can simplify your coding overhead by using ZThread
We can show this by using an Executor instead of explicitly creating Thread objects in MoreBasicThreads.cpp. A LiftOff object knows how to run a specific task; like the
//: c11:ThreadedExecutor.cpp
//{L} ZThread
#include "zthread/ThreadedExecutor.h"
#include "LiftOff.h"
using namespace ZThread;
using namespace std;
int main() {
try {
ThreadedExecutor executor;
for(int i = 0; i < 5; i++)
executor.execute(new LiftOff(10, i));
} catch(Synchronization_Exception& e) {
cerr << e.what() << endl;
}
} ///:~
Note that in some cases a single Executor can be used to create and manage all the threads in your system. You must still place the threading code inside a try block because an Executor’s execute( ) function may throw Synchronization_Exceptions if something goes wrong. This is true for any function that involves changing the state of a synchronization object (starting threads, acquiring mutexes, waiting on conditions, etc.), as you will learn later in this chapter.
The program will exit as soon as all the tasks in the Executor complete.
In the previous example, the ThreadedExecutor creates a thread for each task that you want to run, but you can easily change the way these tasks are executed by replacing the ThreadedExecutor with a different type of Executor. In this chapter, using a ThreadedExecutor is fine, but in production code it might result in excessive costs from the creation of too many threads. In that case, you can replace it with a PoolExecutor, which will use a limited set of threads to execute the submitted tasks in parallel:
//: C11:PoolExecutor.cpp
//{L} ZThread
#include "zthread/PoolExecutor.h"
#include "LiftOff.h"
using namespace ZThread;
using namespace std;
int main() {
try {
// Constructor argument is minimum number of threads:
PoolExecutor executor(5);
for(int i = 0; i < 5; i++)
executor.execute(new LiftOff(10, i));
} catch(Synchronization_Exception& e) {
cerr << e.what() << endl;
}
} ///:~