Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

Interruption

As you might imagine, it s much messier to break out of the middle of a Runnable::run( ) function than it is to wait for that function to get to a test of isCanceled( ) (or some other place where the programmer is ready to leave the function). When you break out of a blocked task, you might need to destroy objects and clean up resources. Because of this, breaking out of the middle of a task s run( ) 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.)[154] 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 <iostream>
#include "zthread/Thread.h"
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, in addition to the insertion into cout, run( ) contains two other 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 call cin.get( ).[155] If you don t give the program an argument, the sleep( ) in main( ) is skipped. Here, 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.[156]

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

If you try to call a function whose mutex has already been acquired, 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 <iostream>
#include "zthread/Thread.h"
#include "zthread/Mutex.h"
#include "zthread/Guard.h"
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;
}
} ///:~
 

The class BlockedMutex has a constructor that acquires the object s own Mutex and never releases it. For that reason, if you try to call f( ), you will always be blocked because the Mutex cannot be acquired. In Blocked2, the run( ) function will be stopped at the call to blocked.f( ). When you run the program you ll see that, unlike the iostream call, interrupt( ) can break out of a call that s blocked by a mutex.[157]

Checking for an interrupt

Note that when you call interrupt( ) on a thread, the only time that the interrupt occurs is when the task enters, or is already inside, a blocking operation (except, as you ve seen, in the case of IO, where you re just stuck). But what if you ve written code that may or may not make such a blocking call, depending on the conditions in which it is run? If you can only exit by throwing an exception on a blocking call, you won t always be able to leave the run( ) loop. Thus, if you call interrupt( ) to stop a task, your task needs a second opportunity to exit in the event that your run( ) loop doesn t happen to be making any blocking calls.

This opportunity is presented by the interrupted status, which is set by the call to interrupt( ). You check for the interrupted status by calling interrupted( ). This not only tells you whether interrupt( ) has been called, it also clears the interrupted status. Clearing the interrupted status ensures that the framework will not notify you twice about a task being interrupted. You will be notified via either a single Interrupted_Exception, or a single successful Thread::interrupted( ) test. If you want to check again to see whether you were interrupted, you can store the result when you call Thread::interrupted( ).

The following example shows the typical idiom that you should use in your run( ) function to handle both blocked and non-blocked possibilities when the interrupted status is set:

//: C11:Interrupting3.cpp {RunByHand}
// General idiom for interrupting a task.
//{L} ZThread
#include <iostream>
#include "zthread/Thread.h"
using namespace ZThread;
using namespace std;
 
const double PI = 3.14159265358979323846;
const double E = 2.7182818284590452354;
 
class NeedsCleanup {
int id;
public:
NeedsCleanup(int ident) : id(ident) {
cout << "NeedsCleanup " << id << endl;
}
~NeedsCleanup() {
cout << "~NeedsCleanup " << id << endl;
}
};
 
class Blocked3 : public Runnable {
volatile double d;
public:
Blocked3() : d(0.0) {}
void run() {
try {
while(!Thread::interrupted()) {
point1:
NeedsCleanup n1(1);
cout << "Sleeping" << endl;
Thread::sleep(1000);
point2:
NeedsCleanup n2(2);
cout << "Calculating" << endl;
// A time-consuming, non-blocking operation:
for(int i = 1; i < 100000; i++)
d = d + (PI + E) / (double)i;
}
cout << "Exiting via while() test" << endl;
} catch(Interrupted_Exception&) {
cout << "Exiting via Interrupted_Exception" << endl;
}
}
};
 
int main(int argc, char* argv[]) {
if(argc != 2) {
cerr << "usage: " << argv[0]
<< " delay-in-milliseconds" << endl;
exit(1);
}
int delay = atoi(argv[1]);
try {
Thread t(new Blocked3);
Thread::sleep(delay);
t.interrupt();
} catch(Synchronization_Exception& e) {
cerr << e.what() << endl;
}
} ///:~
 

The NeedsCleanup class emphasizes the necessity of proper resource cleanup if you leave the loop via an exception. Note that no pointers are defined in Blocked3::run( ) because, for exception safety, all resources must be enclosed in stack-based objects so that the exception handler can automatically clean them up by calling the destructor.

You must give the program a command-line argument which is the delay time in milliseconds before it calls interrupt( ). By using different delays, you can exit Blocked3::run( ) at different points in the loop: in the blocking sleep( ) call, and in the non-blocking mathematical calculation. You ll see that if interrupt( ) is called after the label point2 (during the non-blocking operation), first the loop is completed, then all the local objects are destructed, and finally the loop is exited at the top via the while statement. However, if interrupt( ) is called between point1 and point2 (after the while statement but before or during the blocking operation sleep( )), the task exits via the Interrupted_Exception. In that case, only the stack objects that have been created up to the point where the exception is thrown are cleaned up, and you have the opportunity to perform any other cleanup in the catch clause.

A class designed to respond to an interrupt( ) must establish a policy that ensures it will remain in a consistent state. This generally means that all resource acquisition should be wrapped inside stack-based objects so that the destructors will be called regardless of how the run( ) loop exits. Correctly done, code like this can be elegant. Components can be created that completely encapsulate their synchronization mechanisms but are still responsive to an external stimulus (via interrupt( )) without adding any special functions to an object s interface.

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire