From 7c0e6ead0c4152da8adfedc06bd5ff0f66f5485a Mon Sep 17 00:00:00 2001 From: wolf Date: Mon, 5 May 2003 19:42:26 +0000 Subject: [PATCH] Finish mark-up of this page. git-svn-id: https://svn.dealii.org/trunk@7580 0785d39b-7218-0410-832d-ea1e28bc413d --- deal.II/doc/reports/new-threads/index.html | 148 +++++++++++---------- 1 file changed, 77 insertions(+), 71 deletions(-) diff --git a/deal.II/doc/reports/new-threads/index.html b/deal.II/doc/reports/new-threads/index.html index 498b149cfe..bff69d0e9e 100644 --- a/deal.II/doc/reports/new-threads/index.html +++ b/deal.II/doc/reports/new-threads/index.html @@ -576,17 +576,18 @@ In the implementation above, some tool classes have been used. These are briefly described here.

-

a) The return_value<T> class template

+

a) The return_value<T> class template

-This class stores a value of type T if T is not a reference or -"void". It offers get() and set() functions that get and set the -value. If T is a reference type, then set() is obviously not possible -since references cannot be rebound after construction time. The class -therefore stores a pointer, and set() sets the pointer to the object -the reference references. get() returns the reference again. If T is -"void", then the class is empty and there is only a get() function -that returns void. +This class stores a value of type T if T is not a +reference or void. It offers get() and +set() functions that get and set the value. If T is a +reference type, then set() is obviously not possible since +references cannot be rebound after construction time. The class therefore +stores a pointer, and set() sets the pointer to the object the +reference references. get() then returns the reference again. If +T is void, then the class is empty and there is only +a get() function that returns void.

@@ -614,10 +615,10 @@ that returns void.
 
-

b) The "call" function templates

+

b) The call function templates

-The call function templates take a function pointer, an argument list +The call function templates take a function pointer, an argument list tuple, and the address of the return value object, and call the function with these arguments. Since we have to unpack the argument list, we have to dispatch to different functions, depending on the @@ -638,7 +639,7 @@ number of arguments, in the usual way:

-The Caller class has the following member functions: +The Caller class has the following member functions:

     template <typename RT> struct Caller 
@@ -657,18 +658,18 @@ The Caller class has the following member functions:
 
 
 

-There is a specialization Caller<void> that does not set a return -value, and for each call and do_call function there is a second +There is a specialization Caller<void> that does not set a return +value, and for each call and do_call function there is a second function for member function pointers that takes an object as additional argument.

-

c) mem_fun_ptr

+

c) mem_fun_ptr

-In order to form a pointer to member function for both cases of const -and non-const member functions, we need a simple tool: +In order to form a pointer to member function for both cases of const +and non-const member functions, we need a simple tool:

     template <typename RT, class C, typename ArgList,
               int length = boost::tuples::length<ArgList>::value>
@@ -695,21 +696,21 @@ and non-const member functions, we need a simple tool:
 

-Note that if the second template argument is a "const C", then we mark -the member function "const". The two templates for mem_fun_ptr_helper +Note that if the second template argument is a const C, then we mark +the member function const. The two templates for mem_fun_ptr_helper have to be repeated for every number of arguments that we have in mind. Note also that the specification of the default argument in the -declaration of the general template of mem_fun_ptr_helper saves us -from recomputing it in mem_fun_ptr. +declaration of the general template of mem_fun_ptr_helper saves us +from recomputing it in mem_fun_ptr.

-

d) add_reference for tuples

+

d) add_reference for tuples

The following classes add references to the elements of a tuple, thus -providing the type equivalent of the return value of the boost::tie +providing the type equivalent of the return value of the boost::tie functions. There are probably ways inside boost's tuples library to do this, but I couldn't locate this.

@@ -739,7 +740,7 @@ this, but I couldn't locate this.
 

-The tie_args_helper class is repeated for every number of elements we +The tie_args_helper class is repeated for every number of elements we want to use.

@@ -748,26 +749,26 @@ want to use.

6. Open Problems

+

a) A variable lifetime problem

-a) A variable lifetime problem -The only unsolved semantic problem the author is aware of is the +The only unsolved semantic problem I am aware of at present is the following: if we have a function

-    void f(const int &i);
+    void f(const int &i);
 
then this function can be called as
     f(1);
 
i.e. the compiler creates a temporary and passes its address to -f(). When invoking f() on a new thread, however, as in +f(). When invoking f() on a new thread, however, as in
     spawn (f)(1);
 
-then it is only guaranteed that the call to spawn() does not return +then it is only guaranteed that the call to spawn() does not return before the new thread is started and has copied the arguments to -f(). However, the argument is only the reference to the temporary, not -its value. f() will thus likely observe corrupted values for its +f(). However, the argument is only the reference to the temporary, not +its value. f() will thus likely observe corrupted values for its argument. On the other hand, copying the value is no option either, of course. Since to the author's best knowledge the language does not provide means to avoid taking the address of a temporary, there is @@ -775,9 +776,11 @@ presently no way to avoid this problem. Suggestions for healing it are very welcome.

+ +

b) Forwarding of operator()

-b) Forwarding of operator() -Above, we have defined an overload of spawn for functor-like objects: +Above, we have not defined an overload of spawn for functor-like +objects, even though that would be desirable. One way to do so would be

     template <typename C>
     mem_fun_encapsulator<void,C,boost::tuple<> >
@@ -785,20 +788,21 @@ Above, we have defined an overload of spawn for functor-like objects:
       return spawn (c, &C::operator());
     }
 
-This only works if operator() satisfies the signature +This only works if operator() satisfies the signature
     struct C {    void operator() ();  };
 

-We could add another overload if operator() is const. However, what one +We could add another overload if operator() is +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 -above overload of spawn(). I have not found a way to infer this -information just by using the template parameter C -- it just seems +return type of operator() at the time we declare the return type of +above overload of spawn(). I have not found a way to infer this +information just by using the template parameter C -- it just seems not possible. What would work if it were supported by compilers is a -kind of typeof-operator: +kind of typeof-operator:

     template <typename C>
     typeof(spawn(c,&C::operator()))          // **
@@ -809,14 +813,14 @@ kind of typeof-operator:
 

When seeing the declaration, the compiler would automatically check -which version of the overloaded spawn() function it would call, and -correspondingly take the return type. gcc does support the typeof +which version of the overloaded spawn() function it would call, and +correspondingly take the return type. gcc does support the typeof keyword, but even present CVS snapshots generate an internal compiler error on this construct.

+

c) Using a memory based scheme rather than condition variables

-c) Using a memory based scheme rather than condition variables The scheme using mutices and condition variables to synchronise calling and called thread seems expensive. A simpler approach would be to replace it by letting the creating thread generate an object on the @@ -829,9 +833,10 @@ synchronisation. 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, -the scheme would basically just replace *_encapsulator::operator(), -fire_up, and thread_entry_point: +returns. Apart from replacing ArgReferences by +ArgList in some places, +the scheme would basically just replace *_encapsulator::operator(), +fire_up, and thread_entry_point:

@@ -889,18 +894,19 @@ later on.
 Here are some additional suggestions for discussion:
 

-

a) Conversions between return values

+

a) Conversions between return values

-If f() is a function returning an integer, then the following is +If f() is a function returning an integer, then the following is legal:

     double d = f(arg1, arg2);
 
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 -conversion from thread<T> to thread<void> (i.e.: casting away the -return value)? +Thread<double> and Thread<int> objects? +And do we want to allow a +conversion from Thread<T> to Thread<void> +(i.e.: casting away the return value)?

@@ -909,22 +915,22 @@ Since one can still assign the return value of the thread to a double, double d = thread.return_value();

the only real merit in allowing conversions is in putting threads with -different return value types into a thread_group: +different return value types into a ThreadGroup:
     double f1 ();
     int    f2 ();
  
-    thread_group<double> tg;
+    ThreadTroup<double> tg;
     tg += spawn(f1)();
-    tg += spawn(f2)();    // convert thread<int> to thread<double>
+    tg += spawn(f2)();    // convert Thread<int> to Thread<double>
     tg.join_all ();
 

Being able to do this is probably only syntactic sugar, except for the case where we are not interested in the return values of all threads, -i.e. the conversion thread<T> -> thread<void> seems like the only one -that is really worth it. +i.e. the conversion Thread<T> -> Thread<void> seems +like the only one that is really worth it.

@@ -938,9 +944,9 @@ chains:

-If f() returns 1.5, then t3.return_value() needs to return 1.0. I -believe that such conversions could be implemented, by adding the -types in the chain into a boost::tuple of growing length, and writing +If f() returns 1.5, then t3.return_value() needs to +return 1.0. I believe that such conversions could be implemented, by adding the +types in the chain into a boost::tuple of growing length, and writing 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 @@ -961,10 +967,10 @@ then calling spawn (D(), &B::f);

fails for gcc (but succeeds with Intel's icc). Presumably, gcc is -right: template arguments must match exactly, and D() is of type D, -while &B::f leads to a class type of B. There is no function template -for spawn for which this call can match without a derived-to-base -conversion. We could now change the template +right: template arguments must match exactly, and D() is of type +D, while &B::f leads to a class type of +B. There is no function template for spawn for which this call can +match without a derived-to-base conversion. We could now change the template
     template <typename RT, typename C, typename Arg1>
     mem_fun_encapsulator<RT,C,boost::tuple<Arg1> >
@@ -980,13 +986,13 @@ into
       return mem_fun_encapsulator<RT, C, boost::tuple<Arg1> > (a,fun_ptr);
     }
 
-i.e. introduce another class template A for the type of the +i.e. introduce another class template A for the type of the object. Since the arguments of the constructor to the -mem_fun_encapsulator object are known, the compiler would perform a -derived-to-base conversion for object "a" if necessary. I don't know +mem_fun_encapsulator object are known, the compiler would perform a +derived-to-base conversion for object a if necessary. I don't know whether this is desirable, in particular since also other conversions could happen here that one would not want (in the extreme case -generating a temporary). It is something that should be discussed. +generating a temporary)..

@@ -998,8 +1004,8 @@ When one writes spawn (this, &X::f)
one gets an error that "'this' is not convertible to type X&". One has -to write "*this" instead. It would be simple to have another set of -overloads of spawn() that accepts a pointer instead of a reference, +to write *this instead. It would be simple to have another set of +overloads of spawn() that accepts a pointer instead of a reference, and simply forwards to the existing function. This is just for the lazy people, probably, but it is a common case.

@@ -1009,16 +1015,16 @@ lazy people, probably, but it is a common case.

When a function on a new thread throws an exception, it only -propagates up to one of the two entry_point() functions, then vanishes +propagates up to one of the two entry_point() functions, then vanishes into the run-time system and kills the program. Ideally, we would have a way to pass it over to the main thread. This, however, would need some support from the language. Basically, we would need two operations:

  • clone an exception without knowing its type; we could then in the - entry_point function catch it and stack it somewhere, just like we + entry_point function catch it and stack it somewhere, just like we do for the return value
  • -
  • back on the main thread, the thread::join() function must raise this +
  • back on the main thread, the Thread::join() function must raise this stored exception if there was one, again without knowing its type.
Given how exceptions are implemented usually, the machinery for these -- 2.39.5