From 751f8edffaf644afd29276fe07fe59f34464f767 Mon Sep 17 00:00:00 2001
From: bangerth
-Since the report on
-multithreading was written in 2000, we have put in place a new
+Since the first deal.II report on
+multithreading was written in 2000 (see the list of deal.II
+publications),
+we have put in place a new
implementation of the threading scheme (the first release to contain it is
4.0). The new scheme can do all that you could do before, so the report is in a
sense still valid, but it describes a syntax that is no more used. We will here
@@ -21,6 +23,14 @@ that use it must look like, and for pitfalls to watch out for, please still
refer to the report mentioned above.
+We note that since this report was written, there has been another
+incarnation of support for multicore machines, namely by using the
+Threading Building Blocks and tasks. The documentation module on
+parallel computing, available through the modules page of the deal.II
+manual, explains this new direction in more detail.
+1. Rationale and Introduction
@@ -75,7 +85,7 @@ Regarding the last point, note that any other function is called by
obj.f(arg1, arg2);
Ideally, the following syntax for starting any function on a new
-thread would be nice:
+thread would be nice:
spawn f(arg1, arg2);
spawn obj.f(arg1,arg2);
@@ -97,7 +107,7 @@ this syntax, as well as the following points:
spawn(
) so as to take unbound functions and member
functions, whether virtual or static. Of course, every call needs to be type
-safe, i.e. the exact
+safe, i.e. the exact
same conversions of arguments need to be performed as in a usual call (except
for two additional copies that are necessary).spawn()
needs to return a value that allows us to identify,
@@ -123,7 +133,7 @@ we want to be able to retrieve it once the thread has finished:
collectively, rather than one-by-one.
Basically, the syntax above is all you need to know. It is as simple as
that. The rest of this text, in comparison is very much of technical nature. I
@@ -154,7 +164,7 @@ in the library; all entities that
are not to be used by the user are placed into a namespace
Since objects of type
Note that we need two overloaded versions, for internal
, those to be used are in a namespace
Threads
. The implementation uses Boost's shared_ptr. Some parts of
-the implementation parallel the
+the implementation parallel the
boost::function library, but they are small and taylored to the
particular purpose at hand; in particular, they make heavy use of the
boost::tuple library. We note that the code has in some places already evolved
@@ -185,11 +195,11 @@ threads. For POSIX threads, this class looks as follows:
mutable volatile bool was_joined;
mutable boost::mutex join_mutex;
mutable boost::condition join_condition;
-
+
public:
thread_description_base () : was_joined (false) {};
virtual ~thread_description_base () { /* ... */ };
-
+
void create (void * (*p) (void *), void *d) {
pthread_create (&pt, 0, p, d);
};
@@ -201,7 +211,7 @@ threads. For POSIX threads, this class looks as follows:
if (!was_joined)
pthread_join (pt, 0);
was_joined = true;
- };
+ };
};
@@ -247,7 +257,7 @@ called.
As mentioned, there is exactly one thread_description<RT>
object per created thread. It is accessed using boost::shared_ptr
objects, and references are held from each Thread<RT>
object
-for this thread as
+for this thread as
well as from a wrapper function on the new thread. The object is thus
deleted, when all Thread<RT>
objects for this thread have gone out of
scope (or point to different threads) and the thread itself has
@@ -272,19 +282,19 @@ implementation:
public:
Thread () {};
Thread (const boost::shared_ptr<thread_description<RT> > &td)
- : thread_description (td) {};
-
+ : thread_description (td) {};
+
void join () const { thread_description->join (); };
-
+
RT return_value () {
join ();
return thread_description->ret_val.get();
};
-
+
bool operator == (const thread &t) {
return thread_description == t.thread_description;
};
-
+
private:
boost::shared_ptr<thread_description<RT> > thread_description;
};
@@ -313,20 +323,20 @@ template argument:
template <typename RT = void>
- class ThreadGroup
+ class ThreadGroup
{
public:
ThreadGroup & operator += (const Thread<RT> &t) {
threads.push_back (t);
return *this;
};
-
+
void join_all () const {
for (typename std::vector<Thread<RT> >::const_iterator
t=threads.begin(); t!=threads.end(); ++t)
t->join ();
};
-
+
private:
std::vector<Thread<RT> > threads;
};
@@ -334,7 +344,7 @@ template argument:
Thread<RT>
are freely copyable, there
-is no need
+is no need
to provide an index operator for ThreadGroup
; if you need to index
its elements (for example to get at the return value), use
std::vector<Thread<RT> >
.
@@ -350,7 +360,7 @@ functions. We only show them for unary member functions:
spawn (C &c, RT (C::*fun_ptr)(Arg1)) {
return mem_fun_encapsulator<RT, C, boost::tuple<Arg1> > (c,fun_ptr);
}
-
+
template <typename RT, typename C, typename Arg1>
mem_fun_encapsulator<RT,const C,boost::tuple<Arg1> >
spawn (const C &c, RT (C::*fun_ptr)(Arg1) const) {
@@ -361,9 +371,9 @@ functions. We only show them for unary member functions:
const
and
-non-const
+non-const
member functions. Both create an intermediate object (in the
-internal
+internal
namespace) that will accept arguments in place of the function being
called on the new thread, make sure a new thread is created, copy the
arguments to the new thread's stack, and only then return. The exact
@@ -377,7 +387,7 @@ functions. One would really like to have something also for objects other than
pointers to (member-)functions that provide an
operator()
. However, this doesn't seem to be possible if
operator()
returns something other than void
or takes
-arguments. This
+arguments. This
would need some kind of typeof-operator which is not standard C++. See the
discussion in the Open Problems section.
-The constructor stores the two addresses. If one calls
+The constructor stores the two addresses. If one calls
@@ -446,7 +456,7 @@ arguments, but bind them all together with
spawn(obj, &C::f) (42);
boost::tie
, so that we n
not have different versions of the mem_fun_wrapper
class for different
numbers of arguments. (However, we need a separate partial
specialization of the mem_fun_encapsulator
class for each number of
-function arguments.) The tie_args
template is used to make a version
+function arguments.) The tie_args
template is used to make a version
of the ArgList
type with all reference types; it is described below.
EntryPointClass::entry_point
, where
EntryPoint
is the name of a class that implements this thread
starting function and is passed as a template argument to
-wrapper_base
.
+wrapper_base
.
Before it starts the new thread, it acquires a mutex and
afterwards wait until a condition is signalled before it finishes by
using the thread descriptor object to generate a Thread<RT>
-object.
+object.
@@ -497,9 +507,9 @@ The magic happens in the derived class:
template <typename RT, class C, typename ArgList>
struct mem_fun_wrapper
- : public wrapper_base<RT, mem_fun_wrapper<RT,C,ArgList> >
+ : public wrapper_base<RT, mem_fun_wrapper<RT,C,ArgList> >
{
- typedef typename mem_fun_ptr<RT,C,ArgList>::type MemFunPtr;
+ typedef typename mem_fun_ptr<RT,C,ArgList>::type MemFunPtr;
typedef typename tie_args<ArgList>::type ArgReferences;
mem_fun_wrapper (MemFunPtr mem_fun_ptr,
C &c,
@@ -510,11 +520,11 @@ The magic happens in the derived class:
private:
mem_fun_wrapper ();
mem_fun_wrapper (const mem_fun_wrapper &);
-
+
C &c;
MemFunPtr mem_fun_ptr;
ArgReferences args;
-
+
static void * entry_point (void *arg)
{
const wrapper_base<RT> *w
@@ -524,17 +534,17 @@ The magic happens in the derived class:
MemFunPtr mem_fun_ptr = wrapper->mem_fun_ptr;
C &c = wrapper->c;
ArgList args = wrapper->args;
-
+
boost::shared_ptr<thread_description<RT> >
thread_descriptor = wrapper->thread_descriptor;
-
+
{
boost::mutex::scoped_lock lock (wrapper->mutex);
wrapper->condition.notify_one ();
}
-
+
call (mem_fun_ptr, c, args, thread_descriptor->ret_val);
-
+
return 0;
};
};
@@ -591,7 +601,7 @@ a
get()
function that returns
void
.
- template <typename RT> struct return_value
+ template <typename RT> struct return_value
{
private:
RT value;
@@ -600,7 +610,7 @@ a get() function that returns void
.
void set (RT v) { value = v; }
};
- template <typename RT> struct return_value<RT &>
+ template <typename RT> struct return_value<RT &>
{
private:
RT * value;
@@ -642,7 +652,7 @@ number of arguments, in the usual way:
The Caller
class has the following member functions:
- template <typename RT> struct Caller
+ template <typename RT> struct Caller
{
template <typename PFun, typename ArgList>
static void do_call (PFun fun_ptr,
@@ -727,13 +737,13 @@ this, but I couldn't locate this.
template <typename Tuple>
struct tie_args_helper<Tuple,1>
{
- typedef
+ typedef
boost::tuple<typename add_reference_to_Nth<0,Tuple>::type>
type;
};
template <typename Tuple>
- struct tie_args
+ struct tie_args
{
typedef typename tie_args_helper<Tuple>::type type;
};
@@ -795,7 +805,7 @@ This only works if operator()
satisfies the signature
We could add another overload if operator()
is
-const
. However, what one
+const
. However, what one
would like is an overload for more general signatures. Unfortunately,
this requires that we can infer type and number of arguments and
return type of operator()
at the time we declare the return type of
@@ -834,7 +844,7 @@ The calling thread would then not have to copy the arguments onto its
local stack and signal to the calling thread. It would only have to
delete the memory after the call to the user-supplied function
returns. Apart from replacing ArgReferences
by
-ArgList
in some places,
+ArgList
in some places,
the scheme would basically just replace *_encapsulator::operator()
,
fire_up
, and thread_entry_point
:
@@ -904,7 +914,7 @@ legal:
The question, then, would be: do we want to allow conversions between
Thread<double>
and Thread<int>
objects?
-And do we want to allow a
+And do we want to allow a
conversion from Thread<T>
to Thread<void>
(i.e.: casting away the return value)?
@@ -919,7 +929,7 @@ different return value types into a ThreadGroup
:
double f1 ();
int f2 ();
-
+
ThreadTroup<double> tg;
tg += spawn(f1)();
tg += spawn(f2)(); // convert Thread<int> to Thread<double>
@@ -950,7 +960,7 @@ types in the chain into a boost::tuple
of growing length, and writi
a function that converts a value of the first type of this tuple to
the second, to the third, ..., to the last type in the tuple. However,
a plethora of internal compiler errors has scared me off doing more
-experiments in this direction.
+experiments in this direction.
--
2.39.5