Several methods currently exist that accomplish this task. However, none of these methods lend themselves particularlly well to an object-oriented design; and more importantly none of them are very portable.
Interruption is a concept that is loosely based on Java's interruption mechanism. [Lea 97] Each thread has a internal status associated with it. A thread begins it life without its interrupted status being set. The Thread::interrupt() function is used to set the interrupted status of a thread. A thread in this status will recieve some notifcation in one of two ways:
This helps ensures that a thread is going to recieve notification as soon as possible, at a safe location. Interruption will not occur at unexpected places, and it will not interrupt affect functions outside the ZThreads library. It provides a communication channel, allowing a message to be sent from one thread to the task being run by another. Below, you will see how this can be used to create extremely flexible behavior that is not as easy to recreate when relying only on a platforms native support.
Probably the most common, way to use interruption is to create a Thread that will respond to the request by stopping and exiting. This example demonstrates,
class WorkerThread : public Thread { Mutex lock; Condition workReady; public: WorkerThread() : workReady(lock) { } virtual ~WorkerThread() throw() { } virtual void run() throw() { try { Guard<Mutex> g(lock); workReady.wait(); doSomeWork(); } catch(Interrupted_Exception&) { // Thread interrupted, exit cout << "Interrupted!" << endl; } } void doSomeWork() { // ... } }; int main() { try { // Start the worker thread WorkerThread t; t.start(); // Let it run for a while // User hits a button, want to exit t.interrupt(); t.join(); } catch(Synchronization_Exception&) { // Error starting thread } return 0; }
Task oriented frameworks can be very flexible. Interruption provides a mechanism for create tasks that can be responive to being interrupted. For example, a task can be written so that it can perform whatever cleanup is needed for it self, and can then propogate the exception to whatever ran the task. This code shows a simple worker thread, if any of its tasks are interrupted they perform whatever cleanup is needed and then propogate the exception to the thread running those tasks, causing it to exit.
class Task : public Runnable { public: virtual void run() throw() { try { if(Thread::interrupted()) throw Interrupted_Exception(); Thread::sleep(1000); cout << "Task Complete!" << endl; } catch(Interrupted_Exception&) { // Interrupted, clean up & propogate the exception Thread::current().interrupt(); cout << "Task Interrupted!" << endl; } } }; class WorkerThread : public Thread { MonitoredQueue<Runnable*, FastMutex>& _q; public: WorkerThread(MonitoredQueue<Runnable*, FastMutex>& q) : _q(q) { } virtual ~WorkerThread() throw() { } // ... virtual void run() throw() { Runnable* task = 0; try { while(!Thread::interrupted()) { task = _q.next(); task->run(); delete task; } } catch(Interrupted_Exception&) { // Interrupted waiting for tasks, update a log entry } catch(Cancellation_Exception&) { // Queue canceled, normal exit } if(task) delete task; } }; #define NUM_TASKS 10 int main() { try { MonitoredQueue<Runnable*, FastMutex> q; for(int i=0; i<NUM_TASKS; i++) q.add( RunnablePtr(new Task) ); // Start the worker thread WorkerThread t(q); t.start(); // Let it run for a while Thread::sleep(2000); // User hits a button, want to exit. t.interrupt(); t.join(); } catch(Synchronization_Exception&) { // Error starting thread } return 0; }
Another way to use interruption is to design a task class that interprets interruption to mean that the task should terminate, but not neccessisarily the thread.
class Task : public Runnable { public: virtual void run() throw() { try { if(Thread::interrupted()) throw Interrupted_Exception(); Thread::sleep(1000); cout << "Task Complete!" << endl; } catch(Interrupted_Exception&) { // Interrupted, clean up & don't propogate the exception cout << "Task Interrupted!" << endl; // No exception propogated this time, thread keeps on going } } };
Interruption can also be used to create a way to specifically cancel induvidual tasks, and not neccessarily the entire thread. This can be done easily be extending CancelableTask instead of just Runnable.
class Task : public CancelableTask { public: virtual void doRun() throw() { try { if(Thread::interrupted()) throw Interrupted_Exception(); Thread::sleep(1000); cout << "Task Complete!" << endl; } catch(Interrupted_Exception&) { // Interrupted, clean up & don't propogate the exception cout << "Task Interrupted!" << endl; } } }; #define NUM_TASKS 10 int main() { try { ConcurrentExecutor<FastMutex> executor; vector<Handle<Task>*> pending; // Create the tasks and submit them for(int i=0; i<NUM_TASKS; i++) { pending.push_back(new Handle<Task>(new Task)); executor.execute(*pending.back()); } // User waits a while... Thread::sleep(1000); // ...then decides to cancel a specific job Handle<Task>& job = *pending[5]; job->cancel(); // Request the executor shutdown executor.cancel(); // Clean up the task handles for(vector<Handle<Task>*>::iterator i = pending.begin(); i != pending.end(); ++i) delete *i; } catch(Synchronization_Exception&) { // Error starting thread } return 0; }
The normal semantics for Thread::cancel() are to interrupt the thread and to place thread in canceled status. A thread with canceled status is not forced to shutdown, its not forced to do anything - think of it as a specialized form of interruption.
The Thread::canceled() to function will test the current threads canceled status, returning false if the thread was not canceled and returning true if was. Additionaly, this function will clear a threads interrupted status but it will never clear canceled status.
Executors are implemented so that Thread::interrupt() is a message to a task being run by an Executors thread to abort. Thread::cancel() being based on interruption will also send the message to the task to abort, but will also allow the executor to realize that the last interrupt was a request for the thread to exit.
// A POSIX hook implementing an interruptable accept() sigjmp_buf timeout_jmp; if(sigsetjmp(timeout_jmp, 1) != 0) throw IOInterrupted_Exception(); // Enable the hook ZThread::Thread::interruptKey().set(&timeout_jmp); // call accept() // Disable the hook ZThread::Thread::interruptKey().set(NULL);
For Win32 systems, this exposes a manual reset event handle that will be set each time an interrupt() occurs. This can be used in conjunction with functions like WaitForMutlipleObjects() or WSAEventSelect().
// A POSIX hook implementing an interruptable for accept() HANDLE handles[2]; // Use the hook to get an event to add to the wait set. handles[0] = ZThread::Thread::interruptKey().get(); handles[1] = hSocket; switch(::WaitForMultipleObjects(2,handles,TRUE,INFINITE)) { case WAIT_OBJECT_0: // ZThread::interrupt(), handle it case WAIT_OBJECT_0 + 1: // Socket ready default: // ... }
Many other methods exist for terminating other blocking operations early. For example, a socket can be closed to stop a connect() that is taking too long. Additionally, there are design patterns that can help make this easier to accomplish, reducing the number of places special attention is required (limiting it only to places where poll() or select() is used), by mixing threads with asynchronous I/O. [Schmidt 00]
The implementation of these concepts is freely available as part of the ZThreads library.
[Fleiner et al. 96] |
Fleiner, Claudio, Jerry Feldman, and David Stoutamire ``Killing Threads Considered Dangerous'', POOMA '96 Conference, 1996. |
[Beveridge 99] |
Jim E. Beveridge, Multithreading Applications in Win 32: The Complete Guide to Threads, Addison-Wesley 1996, ISBN 0-201-44234-5 http://cseng.aw.com/book/0,3828,0201442345,00.html |
[Butenhof 97] |
David R. Butenhof, Programming with POSIX Threads, Addison-Wesley 1997, ISBN 0-201-63392-2 http://cseng.aw.com/book/0,3828,0201633922,00.html |
[Lewis et al. 97] |
Bil Lewis, Daniel J. Berg, Multithreaded Programming with Pthreads Prentice Hall, 1997, ISBN 0136807291 http://www.sun.com/books/catalog/Lewis2/ |
[Lea 99] |
Doug Lea, Concurrent Programming in Java: Design Principles and Patterns, Addison-Wesley 1999, ISBN 0-201-31009-0 http://cseng.aw.com/book/0,3828,0201310090,00.html |
[Schmidt 00] | Doug C. Schmidt, Michael Stal, Hans Rohnert and Frank Buschmann, Pattern-Oriented Software Architecture Volume 2 - Patterns for Concurrent and Networked Objects, Wiley 2000, ISBN 0-471-60695-2 http://www.wiley.com/Corporate/Website/Objects/Products/0,9049,104671,00.html |
![]() |
Copyright © 2000 - 2002, Eric Crahen <crahen at cs dot buffalo dot edu> - All rights reserved. |
![]() |
Permission to use, copy, modify, distribute and sell this documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. Eric Crahen makes no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. |