Topic: Future support for multi-threaded C++
Author: "Axter" <google@axter.com>
Date: Fri, 30 Sep 2005 23:46:16 CST Raw View
Gianni Mariani wrote:
> I have been working on a project over the last few months with a team 9
> others and a number of the components have been multi-threaded C++ code
> trying as much as practical to factor out the multi-threaded specific
> components into a library. The library has developed quite nicely and
> is quite straight forward to use except for one niggling issue around
> construction and destruction.
>
> There are a number of facilities that we developed that are "multi
> threaded" in nature and each of them need the developer to perform
> special things in the most derived constructor and destructor, much in
> the same way that virtual inheritance does.
>
> The basic premise is that once destruction starts, the first thing you
> must guarentee is that there will be no further attempts to call the
> object from other threads.
I recommend that the standard adds a synchronized smart pointer
instead.
Example:
http://code.axter.com/sync_ptr.h
IMHO, you can change your code logic to use above smart pointer, and
get the required synchronization automatically.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: gi2nospam@mariani.ws (Gianni Mariani)
Date: Sat, 1 Oct 2005 17:04:11 GMT Raw View
Axter wrote:
..
>
> I recommend that the standard adds a synchronized smart pointer
> instead.
> Example:
> http://code.axter.com/sync_ptr.h
>
> IMHO, you can change your code logic to use above smart pointer, and
> get the required synchronization automatically.
>
I think you missed the point or we have vastly different models in how
we deal with mutlithreading.
How does the sync_ptr deal with a thread object when it's time to destruct ?
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "Branimir Maksimovic" <bmaxa@volomp.com>
Date: Fri, 23 Sep 2005 23:18:57 CST Raw View
Gianni Mariani wrote:
> I have been working on a project over the last few months with a team 9
> others and a number of the components have been multi-threaded C++ code
> trying as much as practical to factor out the multi-threaded specific
> components into a library. The library has developed quite nicely and
> is quite straight forward to use except for one niggling issue around
> construction and destruction.
>
.
> The basic premise is that once destruction starts, the first thing you
> must guarentee is that there will be no further attempts to call the
> object from other threads. Hence if the object was the target of an
> asynchronous callback (e.g. a timer, an asynchronous I/O etc), all of
> these "outstanding requests" must be terminated before destruction may
> continue. Similarly in object construction, the last thing to happen
> must always be to allow the other threads to access the object.
>
......
> A) WITH "default virtual" (assume task is the thread base class).
>
> namespace A {
> struct foo : task
> {
> void work()
> {
> std::cout << "Hello world\n";
> }
> };
> }// namespace A
>
> B) However, without the "default virtual" concept, you need to
> explicitly add a "start" and "join".
>
> namespace B {
> struct foo : task
> {
> void work()
> {
> std::cout << "Hello world\n";
> }
>
> foo()
> {
> task::start();
> }
>
> ~foo()
> {
> task::join();
> }
> };
> } // namespace B
>
This is simply design error. One can't start thread in contructor
of shared object nor can join in destructor, correctly, especially
if it is expected to run some arbitrary user code.
This is a same whether if "start" is called in *last* constructor
or "join" in very *first* destructor. There will be client code
executed
in other thread and that code already accesses partialy constructed
or destructed object.
Solution to this problem is very simple.
template <class Thread>
struct ThreadProxy{
explicit ThreadProxy(Thread* t_):t(t_)
{
t->start();
}
~ThreadProxy()
{
t->join();
// perhaps delete t;
}
Thread* data()const { return t; }
private:
ThreadProxy(const ThreadProxy&);
ThreadProxy& operator=(const ThreadProxy&);
Thread* t;
};
Much like auto_ptr ? can scope_guard (if there is one) do this?
Greetings, Bane.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: gi2nospam@mariani.ws (Gianni Mariani)
Date: Tue, 27 Sep 2005 01:59:27 GMT Raw View
Branimir Maksimovic wrote:
..
>
> This is simply design error. One can't start thread in contructor
> of shared object nor can join in destructor, correctly, especially
> if it is expected to run some arbitrary user code.
Please explain why not.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: "Branimir Maksimovic" <bmaxa@volomp.com>
Date: Tue, 27 Sep 2005 10:20:20 CST Raw View
Gianni Mariani wrote:
> Branimir Maksimovic wrote:
> ..
> >
> > This is simply design error. One can't start thread in contructor
> > of shared object nor can join in destructor, correctly, especially
> > if it is expected to run some arbitrary user code.
>
> Please explain why not.
Section 3.8 says:
"
1 The lifetime of an object is a runtime property of the object. The
lifetime of an object of type T begins
when:
- storage with the proper alignment and size for type T is obtained,
and
- if T is a class type with a nontrivial
constructor (12.1), the constructor call has completed.
The lifetime of an object of type T ends when:
- if T is a class type with a nontrivial
destructor (12.4), the destructor call starts, or
- the storage which the object occupies is reused or released.
"
"
5 Before the lifetime of an object has started but after the storage
which the object will occupy has been allocated34)
or, after the lifetime of an object has ended and before the storage
which the object occupied is
reused or released, any pointer that refers to the storage location
where the object will be or was located
may be used but only in limited ways. Such a pointer refers to
allocated storage (3.7.3.2), and using the
pointer as if the pointer were of type void*, is welldefined.
Such a pointer may be dereferenced but the
resulting lvalue may only be used in limited ways, as described below.
If the object will be or was of a
class type with a nontrivial
destructor, and the pointer is used as the operand of a
deleteexpression,
the
program has undefined behavior. If the object will be or was of a
nonPOD
class type, the program has
undefined behavior if:
- the pointer is used to access a nonstatic
data member or call a nonstatic
member function of the object,
or
- the pointer is implicitly converted (4.10) to a pointer to a base
class type, or
- the pointer is used as the operand of a static_cast (5.2.9) (except
when the conversion is to
void*, or to void* and subsequently to char*, or unsigned char*).
- the pointer is used as the operand of a dynamic_cast
"
This means that if:
struct Thread{
Thread()
{
start(); // thread is started while object is not yet completed
}
~Thread()
{
join(); // thread still runs and accesses this
//while object life time ended
}
void start(); // runs work in other thread
void join(); // waits other thread to finish
void work(); // accesses this
}
Threads are not defined buy standard but it is obvious that
other thread accesses "this" while object is not yet constructed
and after object life time ends. So the definition of functions
start() and work() are restricted buy 3.8.5
(and I know that thread function have to static_cast from void* to
pointer to base in order to call "work", at least in POSIX,
which satisfies three of four conditions for undefined behavior)
This is also in comp.programming.threads faq:
http://www.lambdacs.com/cpt/FAQ.html#Q249
Greetings, Bane.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: gi2nospam@mariani.ws (Gianni Mariani)
Date: Sat, 17 Sep 2005 20:04:08 GMT Raw View
I have been working on a project over the last few months with a team 9
others and a number of the components have been multi-threaded C++ code
trying as much as practical to factor out the multi-threaded specific
components into a library. The library has developed quite nicely and
is quite straight forward to use except for one niggling issue around
construction and destruction.
There are a number of facilities that we developed that are "multi
threaded" in nature and each of them need the developer to perform
special things in the most derived constructor and destructor, much in
the same way that virtual inheritance does.
The basic premise is that once destruction starts, the first thing you
must guarentee is that there will be no further attempts to call the
object from other threads. Hence if the object was the target of an
asynchronous callback (e.g. a timer, an asynchronous I/O etc), all of
these "outstanding requests" must be terminated before destruction may
continue. Similarly in object construction, the last thing to happen
must always be to allow the other threads to access the object.
A new facility in the language, akin to virtual inheritance, would
eliminate most of the "hard to find" errors that we have uncovered.
I want to introduce the concept of "default virtual" inheritance.
The proposed syntax would be somthing like:
class foo : default virtual bar
{
...
};
Like "virtual" inheritance, this would be constructed and destructed by
the most derived constructor and destructor, however it would be the
last object constructed and the first object destructed. This would
simplify many of the basic multithreaded facilities. For example:
A) WITH "default virtual" (assume task is the thread base class).
namespace A {
struct foo : task
{
void work()
{
std::cout << "Hello world\n";
}
};
}// namespace A
B) However, without the "default virtual" concept, you need to
explicitly add a "start" and "join".
namespace B {
struct foo : task
{
void work()
{
std::cout << "Hello world\n";
}
foo()
{
task::start();
}
~foo()
{
task::join();
}
};
} // namespace B
Perhaps the biggest issue for B) is that the practice of placing the
"join()" in the appropriate destructor is somewhat prone to error,
however other subtle issues arise.
struct bar : X::foo // where X is A or B
{
void work()
{
std::cout << "I'm bar";
foo::work();
}
};
In the case of bar inheriting B::foo, you have a race condition or UB,
sometimes only foo::work() is called and sometimes bar::work() is called
and I suspect that sometimes it melts the CPU.
In the case of the "default virtual" (A::foo) inheritance proposal, the
calls to start and join are magically moved to bar's default constructor
and destructor in the same way virtual inheritance works in the current
standard.
Some of the more complex uses of "default virtual" would be where it is
combined with "virtual" inheritance : e.g.
struct B : virtual A
{
..
};
struct D
..
struct C : D, default virtual B
..
I would suspect that the construction for C should call A::A() D::D()
B::B(). As in the current standard, A is constructed first with D
following and the "default virtual B" is last (and as always the reverse
for destruction).
The last sticky issue is when 2 or more classes are "default virtual" i.e.
template <int x> struct A
..
struct C : default virtual A<1>, ... default virtual A<N>
The order of construction should be A<N> ... A<1>, i.e. in reverse order
to which they appear in the inheritance hierarchy.
The "default virtual" facility is impossible to implement without
compiler support other than explicitly adding each an every operation to
construct or destruct in the appropriate place which even in the simple
examples above can be quite limiting.
I posted the following a few months ago and there sadly was no response :-(
http://groups.google.com/group/comp.std.c++/msg/1df7b7bbed29309b
This is basically the same proposal.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: squell@alumina.nl (Marc Schoolderman)
Date: Sat, 17 Sep 2005 23:59:01 GMT Raw View
Gianni Mariani wrote:
I think derivation is the wrong tool for this; Templates make it easy:
template<class Base> struct entry_exit : Base {
entry_exit()
{
// gets constructed last
}
~entry_exit()
{
// gets destructed first
}
};
~Marc.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]
Author: gi2nospam@mariani.ws (Gianni Mariani)
Date: Sun, 18 Sep 2005 03:32:08 GMT Raw View
Marc Schoolderman wrote:
> Gianni Mariani wrote:
>
> I think derivation is the wrong tool for this; Templates make it easy:
>
> template<class Base> struct entry_exit : Base {
> entry_exit()
> {
> // gets constructed last
> }
> ~entry_exit()
> {
> // gets destructed first
> }
> };
My initial thoughts to this are that it does not work when you inherit
multiple types of "async" classes.
e.g.
struct A : task ...
struct B : task ...
struct C : A, B
{
...
};
entry_exit<C> c; // broken ...
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]