Boost ThreadingAbstractWith the desire for a platform independent threading library becoming stronger, Boost has started a project to work on this. A threading library is a major piece of work and there are many different things people speaking of when talking of a threading library. The intention of this document is to provide the general framework, allowing the discussions to concentrate on specific, well defined aspects of the threading library. Eventually it should contain a discussion on different alternatives how to address the various problems including idioms, techniques, and library components, as well as a description of library components and potentially necessary language support, suitable for implementation and possibly standardization.IntroductionFor many problems a solutions involving [pseudo] paralellization seems to be reasonable although it is well known that paralellism produces new problems. Apart from simplifying certain tasks paralell executation can also be employed to make optimal use of multiple processors being available. Although solutions can become simpler using multiple threads, a major new problem is introduced: Data consistency can be dwarfed by having multiple threads modifying the same piece of data at the same time. That is, multi threading introduces race conditions. The whole areas of multi threading can be seen as consisting of basically two tasks:
Although it is tempting (at least to some people) to create a complete and new threading library right away, this cannot be the goal of this effort! Instead, it has to be possible that whatever is created as part of this effort can be integrated with existing threading facilities. This is particularily true for Pthreads, the POSIX approach to multi threading, at least if the multi threading support should ever be integrated with standard C++. One reason for this necessity is the application of the Boost threading facilities in libraries which want to support multi threading but otherwise don't really care about threads. Such components should be applicable independent from the underlying threading facility being used. That is, whatever library components are provided from the Boost Threading Library, these are at first only interfaces. Boost might provide implementation of these interfaces for different threading facilities but it should be possible to [easily] implement these interfaces for whatever is used on a specific platform or in a specific project.
Since the topics of thread manipulation and preventation of race
conditions are widely independent (not completely: certain thread
manipulations might incur
race conditions unless
special care is taken), these topics will be discussed separately.
However, in both cases it should be attempted to support the
solutions to the various problems with idioms and corresponding
library components to make dealing with the problems as easy as
possible.
Thread ManipulationThis section is intentionally left blank since I (ie. Dietmar Kühl) am neither an expert nor interested in this area. Somebody else has to pick this up. If nobody picks up this area, support will be dropped eventually.Preventing Race ConditionsPeople doing multi threaded programming are often that deep into their stuff that they consider deadlocks to be the heart of multi threading problems. Well, it is not! It is actually just a problem introduced by a certain technique to prevent race conditions, namely to protect resources using locks. Although it is almost certain that at the heart of most thread synchronization methods a mutex lock will be used it is worth to think about alternative approaches and to keep the requirements on the basic synchronization mechanisms as low as possible to allow maximum portability and performance. In particular as each use of a mutex incurs some performance penalty which should be avoided where possible for maximum performance.To determine different techniques for preventation of race conditions it has to be determined how the user of some component will interact with threads. This is rather important because as a result of the related decisions the interface to library component will be affected as well as the well as the usage pattern for this component. Here are the basic options how thread safety can be achieved (this does not cover some of the problems arising from the selected approach, eg. deadlocks have also be prevented somehow):
Different of the approaches mentioned above can be used in the
same application. Actually, some approaches might be more suitable
for low level library components while others are more suitable
for higher level library components which internally use the lower
level library components with a different
synchroniztion policy. However, in
discussion it should be made clear what kind of interface is
assumed for an idiom or a technique proposed.
However, since a thread has to know about a resource
to actually use it, any resource created by a thread
which is not made known to other threads does not
cause any problems, as long as the resource is implemented in a
thread safe manner itself! That is, any object
from a thread safe library known to only one
thread does not need any form of consideration. In
particular, any object allocated on the thread's
stack falls into this category as long as neither the
thread nor the object itself register the object
somewhere where it becomes visible to other threads.
Following are some sections outlining general approaches to avoid
certain problems. This is just a start and has to be completed.
Also, there will be separated documents providing more indepth
details on the approaches as these mature.
It would be nice to somehow give programmatic support for detecting
loops which would violate the restriction for partial orders. This
can be done at runtime by tracking certain information when
locking (see throwing deadlock).
Turning this into an enforced compile time features seems to be
impossible. However, if a user kind of registers the resources used
underneath this registry can be used to have the compiler spit out
[probably strange] error messages. The idea is to determine at
compile time some sort of a "level" for each resource used through
the template instantiation mechanism. If there is a loop, the level
cannot be computed and an infinite recursion will have the compiler
spit out some error message like "template nesting level reached".
The implementation of such a feature requires that the underlying
locking mechanism allows a non-blocking attempt
to acquire a lock. If this is provided, when a
lock is to be locked it is first
attempted to lock it non-blockingly. Only if this
fails some work is necessary: The graph has to be searched to
find out whether locking the corresponding
lock would create a deadlock. If
this is the case, an exception is thrown. Otherwise, a blocking
attempt to acquire the lock is made.
For example, consider heap allocations: There is basically one pool
for heap memory. This pool can be accessed using the standard
Basically this means that a service is split into two parts: One
part shared amoung threads and one part local to
each thread using this service.
But where is an immutable service useful as it can't modify anything?
Well, it can modify things! It is only the service itself which has
to be immutable. For example, the facets in the standard localization
library are immutable (with respect to the service they provide; there
is a mutable reference count but this does not affect normal processing
at all) but they are mutating what is passed to them as argument. Of
coure, the resources identified by the arguments have to be protected
against race conditions if they are shared
between several threads. However, if they are not shared
there is no need to protect anything.
Pthreads Programming, Bradford Nichols, Dick Buttlar, Jaqueline Proulx Farell, O'Reilly & Associates, ISBN 1-56592-115-1. The FAQ of comp.programming.threads is found at <http://www.LambdaCS.com/newsgroup/FAQ.html>
|