Becoming blocked
A thread is blocked when it cannot continue running. A thread can become blocked for the following reasons:
· You’ve put the thread to sleep by calling sleep(milliseconds), in which case it will not be run for the specified time.
· You’ve suspended the execution of the thread with wait( ). It will not become runnable again until the thread gets the signal( ) or broadcast( ) message. We’ll examine these in a later section.
· The thread is waiting for some I/O to complete.
· The thread is trying to enter a block of code that is guarded by a mutex, and that mutex has already been acquired by another thread.
The problem we need to look at now is this: sometimes you want to terminate a thread that is in a blocked state. It can be blocked for any of the reasons in the above list (except for IO, as you’ll see). If you can’t wait for it to get to a point in the code where it can check a state value and decide to terminate on its own, you have to abort the thread out of its blocked state.
Interruption
As you might imagine, it’s much messier to break out of the middle of a loop than it is to wait for the loop to get to a test of isCanceled( ) (or some other place where the programmer is ready to leave the loop). When you break out of a blocked task, you might be breaking out of your run( ) loop in a place where you must destroy objects and clean up resources. Because of this, breaking out of the middle of a run( ) loop in a task is more like throwing an exception than anything else, so in ZThreads, exceptions are used for this kind of abort. (This walks the fine edge of being an inappropriate use of exceptions because it means you are often using them for control flow.) To return to a known good state when terminating a task this way, carefully consider the execution paths of your code and properly clean up everything inside the catch clause. We’ll look at these issues in this section.
To terminate a blocked thread, the ZThread library provides the Thread::interrupt( ) function. This sets the interrupted status for that thread. A thread with its interrupted status set will throw an Interrupted_Exception if it is already blocked or it attempts a blocking operation. The interrupted status will be reset when the exception is thrown or if the task calls Thread::interrupted( ). As you’ll see, Thread::interrupted( ) provides a second way to leave your run( ) loop, without throwing an exception.
Here’s an example that shows the basics of interrupt( ):
//: C11:Interrupting.cpp
// Interrupting a blocked thread.
//{L} ZThread
#include "zthread/Thread.h"
#include <iostream>
using namespace ZThread;
using namespace std;
class Blocked : public Runnable {
public:
void run() {
try {
Thread::sleep(1000);
cout << "Waiting for get() in run():";
cin.get();
} catch(Interrupted_Exception&) {
cout << "Caught Interrupted_Exception" << endl;
// Exit the task
}
}
};
int main(int argc, char* argv[]) {
try {
Thread t(new Blocked);
if(argc > 1)
Thread::sleep(1100);
t.interrupt();
} catch(Synchronization_Exception& e) {
cerr << e.what() << endl;
}
} ///:~
You can see that run( ) contains two points where blocking can occur: the call to Thread::sleep(1000) and the call to cin.get( ). By giving the program any command-line argument, you tell main( ) to sleep long enough that the task will finish its sleep( ) and move into the cin.get( ). If you don’t give the program an argument, the sleep( ) in main( ) is skipped. In this case, the call to interrupt( ) will occur while the task is sleeping, and you’ll see that this will cause Interrupted_Exception to be thrown. If you give the program a command-line argument, you’ll discover that a task cannot be interrupted if it is blocked on IO. That is, you can interrupt out of any blocking operation except IO.
This is a little disconcerting if you’re creating a thread that performs IO, because it means that I/O has the potential of locking your multithreaded program. The problem is that, again, C++ was not designed with threading in mind; quite the opposite, it effectively pretends that threading doesn’t exist. Thus, the iostream library is not thread-friendly. If the new C++ standard decides to add thread support, the iostream library may need to be reconsidered in the process.
Blocked by a mutex
In the previous list of ways to become blocked, the last one happens when you’re trying to call a function whose mutex has already been acquired. In this situation, the calling task will be suspended until the mutex becomes available. The following example tests whether this kind of blocking is interruptible:
//: C11:Interrupting2.cpp
// Interrupting a thread blocked
// with a synchronization guard.
//{L} ZThread
#include "zthread/Thread.h"
#include "zthread/Mutex.h"
#include "zthread/Guard.h"
#include <iostream>
using namespace ZThread;
using namespace std;
class BlockedMutex {
Mutex lock;
public:
BlockedMutex() {
lock.acquire();
}
void f() {
Guard<Mutex> g(lock);
// This will never be available
}
};
class Blocked2 : public Runnable {
BlockedMutex blocked;
public:
void run() {
try {
cout << "Waiting for f() in BlockedMutex" << endl;
blocked.f();
} catch(Interrupted_Exception& e) {
cerr << e.what() << endl;
// Exit the task
}
}
};
int main(int argc, char* argv[]) {
try {
Thread t(new Blocked2);
t.interrupt();
} catch(Synchronization_Exception& e) {
cerr << e.what() << endl;
}
} ///:~