Motivation for Interruptable Threads
Introduction
The purpose of this document is to explain some of the motivation for creating
ZThreads. This document is long overdue, but hopefully it will help to give
people a better understandsing of the core of the library and how to use it.
Other articles will disccuss porting the library, as well as using the higer
level constructs it provides.
Motivation for Creation of ZThreads
I have a strong interest in object-oriented design and in design patterns.
I also do alot of programming on different systems, some of that programming
inevitably involves writting multi-threaded code. C++ doesn't have any built in
support for threads. Various platforms provide various methods for creating
and managing threads, none of which are very portable. For example, there are POSIX
threads, Win32 threads and so on. All of these models of threading are really quite
different, which makes writing portable that uses threads somewhat of a challange.
My goal was to abstract
away the need to rely on any one specific thread model. The primary goals are
to create portable and to complete abstract the system specific details involved
in writing multi-threaded code.
Once this kind of support is available, much more time can be spent on evolving
design patterns for using multi threaded. Developing new patterns, such as
the Executor and Future patterns shoudl be beneficial to everyone.
Should C++ have built in support for threads? I'm not sure that is completely
necessary. What would go a long way towards improving support for code that threads
would be providing a way to deal with related issues, such as a few standard
atomic operations (TAS, CAS, DCAS, ...) and perhaps chaning volatile or mutable
to ask the compiler to insert a memory barrier. Something like that, coupled with
a way to handle the order certain objects are destroyed in (a more flexible
atexit() function) would add alot of power to the language and to what you can
do with threads without having to pick any one model for multithreading. This
library demonstrates that flexible, portable support for threads, including a
cancellation-like mechanism can be built on top of current thread models.
Motivation for Interrupting Threads
There are several reasons I choose to create an interruptible Thread model.
Often, there is a need to abort a blocking operation. For example, a thread
might be put to sleep on a condition variable only to have some event occur
in a program that would make it desirable to wake that thread up again. Each
platform provides a method for accomplishing this, but each method is very
closely tied to that platform. The two major models of this are POSIX
cancellation and Win32 WaitForMultipleObjects(). The following explains
the challanges in emulating the behavior of either model and the portable
alternative ZThreads provides.
Problems with POSIX Cancellation:
Cancellation is a C based mechanism; because of this it does not safely
interact with exceptions. A thread cancelled within a try block will not
neccessarily unwind the stack correctly; this makes it extremely difficult to write
safe code that uses cancellation with C++ exceptions.
This library is intended to be object-oriented. Pushing function
pointers onto a clean up stack can be somewhat awkward to incorporate nicely
into an object-oriented design.
|
Cancellation is not completely portable. Although it is standard, different
operating systems have different levels of support for this mechanism. Some
cancellation points are optional, and some cancelltions are non-standard.
When writing code for a single system it is not at all difficult to lookup
the supported cancellation points. However, writing portable code would
require finding out about each platforms cancellation support. This is
tedious at best. If this is not done correctly it is very possible to write
code on one system that does not behave the same way on another system, or
to write code that relies on a cancellation that exists on one system, but not
on another.
It is possible to turn cancellation on and off for various regions of code,
but this really leads to the same problem. A programmer can easily forget
to do this and get the expected behavior on one system only to find that
porting another system has brought about a series of unexpected cancellation
problems.
This library is all about abstraction and portability. An important goal is
to remove the burden of dealing with the specific details of each system
from the programmer.
|
Cancellation is an invasive mechansim. Invoking the cancellation on a thread
has the potentialy to disturb non-pthreads functions. This is by design
and again is not an issue when writing code for one system. However, it
becomes a tricky issue when writing portable code.
Emulating this kind of behavior would require creating wrappers for functions
that should act as cancellation points for systems that don't support POSIX
cancellation (Win32). This in completely outside the scope of a thread library.
This libraries focus is on portable threads, not on creating a portable
version of libc that supports cancellation.
Additionally, the I/O models uses by different systems are very different.
For example, Win32 uses compeletion ports, event handles, winsock, etc.
It is not possible to choose a single set of functions that are appropriate
cancellation points for all systems because of these differences.
|
Problems with Win32 WaitForMultipleObjects():
WaitForMultipleObjects(), like POSIX cancellation, is also not C++ exception
based. Although it does not have the same problems in interacting with
exception based code, it can still be a bit more awkward to deal with
error codes and switch statements.
|
WaitForMultipleObjects() is based on Windows handles. It is very versatile
because it allows you to wait on a set of handles and to the end that wait
when any of those handles is signaled. Just about everything in Windows
uses handles making it possible to do very interesting things like wait on
thread handle, a socket handle, a file handle and an event handle.
The main drawback is that on other systems not everything uses the same
opaque type to create generic handles. For example, on POSIX file handles (int),
thread handles (pthread_t) and mutex handles (pthread_mutex_t) are
unrelated. It very difficult to create a POSIX implementation of that function
with the same versatility. Without the ability to wait on several kinds of
handles a big part of the usefulness of WaitForMultipleObjects() would be
lost.
|
Another difficulty in using this type of approach is that, just like POSIX
cancelation, it can be easy to misuse. Programmers must be very aware of
the consequences involved in using event handles correctly. While this can
be chalked up to programmer responsibility, this library is an abstraction.
Its intended to take the focus away from these lower level details.
|
Interruption as a Solution:
To deal with these problems some type of solution was needed. First and foremost,
this solution should be 100% portable. The primary goal is portability and abstraction.
Remember that this library is not intended to replace pthreads or win32 threads or
any operating systems native thread support. Its meant to take advantage of that
support and to extend it to provide a truely portable abstraction for using threads.
Interruption is a completely C++ exception based mechanism. Stack unwinding is done correctly,
and there are no issues involved with using interruption safely. Its very natural
to use interruption if you are used to writing exception based code. No function
pointers are required.
|
Interruption is a completely portable mechanism. There are no issues involved
with unexpected behavior when porting interruption-based code to another system.
A program using interruption will remain consistent n its behavior.
|
Interruption is far more flexible. Cancellation is a terminal operation, while
interruption is not. Cancellation has cancel-and-exit semantics, which is
to say that once a thread is cancelled it has to die. This can be limiting in
what can be accomplished.
Cancellation was not intended to be a communication mechanism.
Interruption, on the other hand, is temporary. A thread will recieve an exception
when interrupted, but it will reset to a normal state once that exception is thrown;
it is not forced to terminate. The thread can go on to do other things,
which adds a huge amount of flexibility to what can be accomplished. If a programmer
prefers to use cancel-and-exit semantics, they can take an exception thrown
as a result of interruption to mean they need to shut down and exit. However, if they
want to expliot the flexiblity of interruption for other purposes, that can also be
accomplished.
Interruption is an important communication mechanism.
|
Interruption is not an invasive mechanism. Interruption is focused on threads
and thread related operations. by default it will not affect functions outside
of the ZThreads library. This is important for portability.
However, interruption does provide a hook that lets a programmer tie
some system specific behavior to he interruption mechanism. This can be used to
make interruption have some effect on functions external to the ZThreads library
if this is desired.
|
This is the only, and first, free library that provides this kind of portable
C++ support.
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.
|
|