<p>
-Since the <a href="../multithreading/index.html" target="body">report on
-multithreading</a> 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
refer to the report mentioned above.
</p>
+<p>
+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.
+</p>
+
<h3>1. Rationale and Introduction</h3>
obj.f(arg1, arg2);
</pre></code>
Ideally, the following syntax for starting any function on a new
-thread would be nice:
+thread would be nice:
<code><pre>
spawn f(arg1, arg2);
spawn obj.f(arg1,arg2);
<ul>
<li>Overloading <code>spawn(</code>) 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).</li>
<li><code>spawn()</code> needs to return a value that allows us to identify,
collectively, rather than one-by-one.</li>
</ul>
</p>
-
+
<p>
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
are not to be used by the user are placed into a namespace
<code>internal</code>, those to be used are in a namespace
<code>Threads</code>. 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
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);
};
if (!was_joined)
pthread_join (pt, 0);
was_joined = true;
- };
+ };
};
</pre></code>
As mentioned, there is exactly one <code>thread_description<RT></code>
object per created thread. It is accessed using <code>boost::shared_ptr</code>
objects, and references are held from each <code>Thread<RT></code> 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 <code>Thread<RT></code> objects for this thread have gone out of
scope (or point to different threads) and the thread itself has
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;
};
<code><pre>
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;
};
<p>
Since objects of type <code>Thread<RT></code> are freely copyable, there
-is no need
+is no need
to provide an index operator for <code>ThreadGroup</code>; if you need to index
its elements (for example to get at the return value), use
<code>std::vector<Thread<RT> ></code>.
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) {
<p>
Note that we need two overloaded versions, for <code>const</code> and
-non-<code>const</code>
+non-<code>const</code>
member functions. Both create an intermediate object (in the
-<code>internal</code>
+<code>internal</code>
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
pointers to (member-)functions that provide an
<code>operator()</code>. However, this doesn't seem to be possible if
<code>operator()</code> returns something other than <code>void</code> 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.
</p>
template <typename RT, typename C, typename ArgList>
class mem_fun_encapsulator<RT,C,ArgList,1> {
- typedef typename mem_fun_ptr<RT,C,ArgList>::type MemFunPtr;
-
+ typedef typename mem_fun_ptr<RT,C,ArgList>::type MemFunPtr;
+
public:
mem_fun_encapsulator (C &c, MemFunPtr mem_fun_ptr)
: c (c), mem_fun_ptr(mem_fun_ptr) {};
-
- Thread<RT>
+
+ Thread<RT>
operator() (typename boost::tuples::element<0,ArgList>::type arg1) {
return mem_fun_wrapper<RT,C,ArgList> (mem_fun_ptr, c,
boost::tie(arg1)).fire_up ();
};
-
+
private:
C &c;
MemFunPtr mem_fun_ptr;
</p>
<p>
-The constructor stores the two addresses. If one calls
+The constructor stores the two addresses. If one calls
<code><pre>
spawn(obj, &C::f) (42);
</pre></code>
not have different versions of the <code>mem_fun_wrapper</code> class for different
numbers of arguments. (However, we need a separate partial
specialization of the <code>mem_fun_encapsulator</code> class for each number of
-function arguments.) The <code>tie_args</code> template is used to make a version
+function arguments.) The <code>tie_args</code> template is used to make a version
of the <code>ArgList</code> type with all reference types; it is described below.
</p>
Thread<RT> fire_up () {
thread_descriptor
= DescriptionPointer(new typename thread_description<RT>());
-
- boost::mutex::scoped_lock lock (mutex);
+
+ boost::mutex::scoped_lock lock (mutex);
thread_descriptor->create (&EntryPointClass::entry_point,
(void *)this);
condition.wait (lock);
-
+
return thread_descriptor;
}
-
+
protected:
typedef boost::shared_ptr<thread_description<RT> >
DescriptionPointer;
-
+
DescriptionPointer thread_descriptor;
-
- mutable boost::mutex mutex;
+
+ mutable boost::mutex mutex;
mutable boost::condition condition;
};
</pre></code>
the starting point is <code>EntryPointClass::entry_point</code>, where
<code>EntryPoint</code> is the name of a class that implements this thread
starting function and is passed as a template argument to
-<code>wrapper_base</code>.
+<code>wrapper_base</code>.
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 <code>Thread<RT></code>
-object.
+object.
</p>
<p>
<code><pre>
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,
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
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;
};
};
</p>
<code><pre>
- template <typename RT> struct return_value
+ template <typename RT> struct return_value
{
private:
RT value;
void set (RT v) { value = v; }
};
- template <typename RT> struct return_value<RT &>
+ template <typename RT> struct return_value<RT &>
{
private:
RT * value;
The <code>Caller</code> class has the following member functions:
<code><pre>
- template <typename RT> struct Caller
+ template <typename RT> struct Caller
{
template <typename PFun, typename ArgList>
static void do_call (PFun fun_ptr,
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;
};
</p>
<p>
We could add another overload if <code>operator()</code> is
-<code>const</code>. However, what one
+<code>const</code>. 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 <code>operator()</code> at the time we declare the return type of
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 <code>ArgReferences</code> by
-<code>ArgList</code> in some places,
+<code>ArgList</code> in some places,
the scheme would basically just replace <code>*_encapsulator::operator()</code>,
<code>fire_up</code>, and <code>thread_entry_point</code>:
</p>
</pre></code>
The question, then, would be: do we want to allow conversions between
<code>Thread<double></code> and <code>Thread<int></code> objects?
-And do we want to allow a
+And do we want to allow a
conversion from <code>Thread<T></code> to <code>Thread<void></code>
(i.e.: casting away the return value)?
</p>
<code><pre>
double f1 ();
int f2 ();
-
+
ThreadTroup<double> tg;
tg += spawn(f1)();
tg += spawn(f2)(); // convert Thread<int> to Thread<double>
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.
</p>