Topic: C++ and threads (invariant issues, actually)
Author: Alexander Terekhov <terekhov@web.de>
Date: Tue, 16 Apr 2002 01:49:42 GMT Raw View
John Nagle wrote:
[...]
> C++ really does need language-level thread
> support, because there are so many language-level
> thread issues, of which this is only one. Yes,
> you can do everything with OS calls, but the
> language isn't giving you any help in getting it
> right. Are there any good designs for this being
> proposed?
FYI:
http://groups.google.com/groups?as_umsgid=3CA9EDE3.2227EE4A%40web.de
"....
The next step could be that "full"/higher level concurrency
support via the language constructs incorporating the ideas
the Butenhof is talking about (AFAICT) from things like Ada
and perhaps some "research" works (not so widely popular,
"robust", etc) -- uC++:
ftp://plg.uwaterloo.ca/pub/uSystem
and these two papers[1]:
ftp://ftp.dsg.cs.tcd.ie/pub/doc/dsg-86.ps.gz
(Ciaran McHale, Synchronisation in Concurrent,
Object-oriented Languages: Expressive Power,
Genericity and Inheritance, 1994)
http://www.doc.ic.ac.uk/~sjg/thesis-mybg.ps.gz
(Michael Ya'akov Ben-Gershon, An Object-Oriented
Approach to Concurrency and Synchronisation, 2000)
....
[1] Just in case you will have some problems with
the links above (reportedly do NOT always
work), I've got PDFs on my site too:
http://www.terekhov.de/OO-Concurrence/dsg-86.pdf
(McHale's paper)
http://www.terekhov.de/OO-Concurrence/thesis-mybg.pdf
(Ben-Gershon's paper)
Please let me know if that's somehow
"illegal" and I'll just pull these
PDFs from my site away, immediately."
regards,
alexander.
---
[ 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: "Hillel Y. Sims" <usenet@phatbasset.com>
Date: Fri, 12 Apr 2002 05:44:11 GMT Raw View
"John Nagle" <nagle@animats.com> wrote in message
news:3CB5E42C.8050907@animats.com...
> If you insist that the invariant be made true
> when an exception is thrown, you have to have
> a try/catch block around every member function with
> an invariant. The catch clause must then
> "repair" the invariant, then reraise the
> exception. This is conceptually sound, but bulky.
> C++ programmers won't like it and probably won't
> do it right.
>
> That's what leads to wanting to mark an object
> as "broken".
>
use RAII-style and swap() techniques to avoid this problem easily! One
really useful tool is Andrei Alexandrescu / Petru Marginen's ScopeGuard /
ON_BLOCK_EXIT package -- it is really an amazing help to achieve
"finally"-like protection for almost anything... A colleague of mine has
been playing with it a lot lately and figuring out some really cool
idioms -> check this out, he just showed me this one today, I thought it was
great:
void FileObj::process_stream()
{
// FileObj has a malloc'd buffer of data which needs to be reprocessed
somewhat:
char* newbuf = malloc(size());
if (newbuf == 0)
throw std::bad_alloc();
// apply try/finally-style protection to newbuf in an even nicer style
than finally:
ON_BLOCK_EXIT(free, ByRef(newbuf)); // (boost has a ByRef-like utility
too)
// now do various data buffer processing stuff here that may or may not
throw
...
// now do nothrow commit:
std::swap(newbuf, m_pDataBuf); // ON_BLOCK_EXIT "finally" will
automatically clean up the old memory now!!
}
I don't know about you, but I thought that was really great. Strong
exception-safety with malloc and no catch() blocks and without writing a
custom handle class. It doesn't get much better than that when dealing with
traditionally unsafe code. Kudos to him and Andrei and Petru. :-)
> One way to think about "broken" objects is
> to think of them as being locked. (This is,
> after all, the "threads" thread). Suppose
> the object with an invariant has a lock.
> (Assume Java "synchronized" semantics for this
> discussion.) Then
> something called from a member function throws
> an exception, and control exits the object with
> the lock set. Now the object is in an invalid
> state, but it can't be entered, because that would
> cause a deadlock.
(Hey, we're back on threads, I love threads! ;-)
A large focus of boost.threads is to help make this concept easier and
safer. All boost.thread locks are done through RAII-style scoped-lock
objects; they can never remain locked permanently (unless an exception is
unhandled all the way through main(), in which case the process dumps and it
probably doesn't really matter much anyhow). No garbage-collection required.
(Numerous other threading packages have previously implemented similar
scoped-lock concepts as well, which I understand helped serve to inspire the
boost implementation.)
>
> Now we have an object that can't be entered.
> We probably can't delete it, either, because delete
> would deadlock on the lock. But we've protected
> the invariant; control can't get into the broken
> object. At this point, garbage collection starts
> to look attractive, because a garbage collector
> can tell whether control is in an object. If
> an object is unreachable yet locked, and control
> isn't inside it, it can safely be deleted.
> (This assumes the object doesn't have a
> destructor. GC and destructors don't mix well,
> anyway.)
>
> So there's a way out of this mess for
> Java-type semantics, but not for current
> C++ semantics.
Sure there is, see boost.threads (or any scoped-lock based threads package)
:-) I think java semantics are pretty weak in general really, you have to
use finally blocks everywhere to truly make your code exception-safe (not
just threads, any exceptions); that's much more onerous than automatic c++
destructor-based semantics, imho (and now there is even ON_BLOCK_EXIT to be
the C++ "finally" when there is no automatic destructor otherwise available
anyhow, no extra language support necessary, other than a compliant compiler
;-).
>
> C++ really does need language-level thread
> support, because there are so many language-level
> thread issues, of which this is only one. Yes,
> you can do everything with OS calls, but the
> language isn't giving you any help in getting it
> right. Are there any good designs for this being
> proposed?
>
Boost.threads seems like a fairly decent take on a threading library, at the
least (and can be implemented in terms of all sorts of platform-specific
APIs), which does help get a lot of stuff right automatically (without going
to the next level of semantics where the language could actually figure out
what you're doing and automatically protect everything without any manual
effort, but that seems like totally next-generation wish-list stuff really).
But there are some aspects of core language support that are absent and
really do need to be addressed to make threading really work right and
smoothly.
Here's my wish list:
- Static object allocation needs to be formally defined to be thread-safe on
platforms where it is appropriate (as opposed to the current
implementation-specific it-is-if-you're-lucky situation). This would
probably eliminate the need for the posix-like "once-function" workaround
provided in boost.threads (implemented in terms of platform-specific API
functions).
eg, in current C++98, this idiom may or may not be thread-safe on your
particular platform:
class Foo { ... }; // has non-trivial ctor
void func()
{
static Foo f;
...
}
Is this object construction properly synchronized? It is on my platform
(hooray for Compaq! A cpu-spinlock is applied around an implicit boolean
"constructed"-flag to synchronize the object construction with minimal
overhead). How 'bout yours? Without a standard-required mandate, the only
portable way to guarantee that is via use of a less-encapsulated /
more-intrusive / more-complicated workaround "once-function" mechanism.
- If you can conveniently create threads and join on them (wait for them to
complete), then it seems like you ought to be able to kill them conveniently
(but safely) too, and a thread-cancellation mechanism needs to be (and can
be) standardized -- on many platforms currently, posix- or windows-style
thread cancellation is inherently unsafe with standard C++ semantics (object
destructors may never be called appropriately, leaving the process in all
sorts of messed up states, at the least leaking memory). However, it can be
fully integrated into C++ without any core language changes, other than
mandating that "C++ thread"-cancellation is represented as an exception
(synchronous or asynchronous; 99% of the time synchronous though) of some
sort in the target thread (as has been discussed in previous messages, and
there are various current C++ implementations using posix-style threads that
already do exactly this and can serve as working examples for how it could
work in a standard way).
In addition, some additional library support is needed to provide a
necessary thread-cancellation-guard scope-based object which can be used to
make nothrow scopes thread-safe and exception-safe (in order to prevent
thread-cancel exception from being thrown inside code which cannot
appropriately handle it - catch (...) {} is invalid for handling
thread-cancel exceptions) - core language integration could probably
actually make handling this concept easier, but it can be accomplished
minimally with only library code support.
thanks,
hys
--
Hillel Y. Sims
hsims AT factset.com
---
[ 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: "Garry Lancaster" <glancaster@ntlworld.com>
Date: Wed, 10 Apr 2002 16:23:24 GMT Raw View
Daniel Miller:
> >>>>Deducing from your always-use-assert-macro-
> >>>>instead-of-throwing-logic-error claim,
Garry Lancaster:
> >>>Ahem. I didn't write that. I wrote:
> >>>
> >>>"Once you accept the wisdom of programming with
> >>>the exception safety guarantees you can no longer
> >>>accept that it's generally acceptable to throw an
> >>>exception when class invariants are violated. (This
> >>>is the sort of thing assertions are for, IMHO.)"y
> >>it's formulated.
John Nagle:
> If an invariant is violated, the object is
> broken.
It's worse than that. The *program* is broken.
> So what do you do then? Here are the main
> options.
>
> - Exit the program. Simple, but not always appropriate.
This is the least worst option. Preferably, you should
exit with as much useful diagnostic information as
possible. This is why I like asserts here.
> - Mark the object as broken, and throw on all future entries to
> the object, other than the destructor.
>
> Sound, but not well supported in C++.
> No standard way to mark an object as "broken".
> No simple way to enforce a "broken" flag
> Destructor can't assume invariant is true.
....and for that last reason, not sound, since the
typical destructor has to assume that invariants
still hold. Furthermore, a broken invariant tells
you your program is broken, it does not tell you
the extent of your breakage: it could well
extend beyond the object that detected it. If the
program was totally rigourous in its checking
you could be sure the breakage was localised,
but if the program was totally rigourous, it
wouldn't break an invariant in the first place,
would it? ;-) Broken invariants are typically a
symptom of a problem, not the problem itself.
Once you start having "broken" flags, you are
legitimising the broken invariant state to such an
extent that it becomes a legal program state, and
the term "invariant" seems much less appropriate.
> - Throw, leaving object with an invalid invariant, and
> rely on caller to delete.
>
> Probably most common solution today.
> Object is relying on its caller for its own
> integrity, which is unsound.
It's not the most common solution in my programs,
but I can't speak for anyone else ;-)
I agree it is unsound, but not for the reason you
state: the problem is not that the object is relying
on the caller for integrity it is that it has no
integrity; not even deleting it is guaranteed to
work correctly.
> That summarizes where we are, I think.
>
> As usual, the most sound solution is bulky to write
> in C++.
The only truly sound "solution" - the only way a program
will be correct - is if it never breaks an invariant in the first
place. A broken invariant reveals a coding or design
error (or, very occasionally, a hardware error). If you
happen to discover a broken invariant the correct
response is to fix the problem that caused it, which is not
something that can be done by the program itself
at run-time. Should you happen to detect a broken
invariant at run-time the best response is to
very loudly alert the user/support team of the
problem. Whatever your program does after it discovers
it is broken is unlikely to be correct, so the simplest
option is to terminate it before it makes matters
worse. This is most closely related to your first option,
not the second option.
Kind regards
Garry Lancaster
Codemill Ltd
Visit our web site at http://www.codemill.net
---
[ 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: John Nagle <nagle@animats.com>
Date: Thu, 11 Apr 2002 19:24:36 GMT Raw View
Garry Lancaster wrote:
> The only truly sound "solution" - the only way a program
> will be correct - is if it never breaks an invariant in the first
> place.....
The problem we're discussing is what to do when
an exception is thrown during the execution of
a member function with an invariant (insofar
as "invariant" has meaning in C++). Invariants
are required to be true only at entry and exit of the
function, not during its execution. Yet an exception
can be thrown at any time during function execution.
The invariant will not, in general, be true at
the time the exception is raised.
Hence the problem.
If you insist that the invariant be made true
when an exception is thrown, you have to have
a try/catch block around every member function with
an invariant. The catch clause must then
"repair" the invariant, then reraise the
exception. This is conceptually sound, but bulky.
C++ programmers won't like it and probably won't
do it right.
That's what leads to wanting to mark an object
as "broken".
One way to think about "broken" objects is
to think of them as being locked. (This is,
after all, the "threads" thread). Suppose
the object with an invariant has a lock.
(Assume Java "synchronized" semantics for this
discussion.) Then
something called from a member function throws
an exception, and control exits the object with
the lock set. Now the object is in an invalid
state, but it can't be entered, because that would
cause a deadlock.
Now we have an object that can't be entered.
We probably can't delete it, either, because delete
would deadlock on the lock. But we've protected
the invariant; control can't get into the broken
object. At this point, garbage collection starts
to look attractive, because a garbage collector
can tell whether control is in an object. If
an object is unreachable yet locked, and control
isn't inside it, it can safely be deleted.
(This assumes the object doesn't have a
destructor. GC and destructors don't mix well,
anyway.)
So there's a way out of this mess for
Java-type semantics, but not for current
C++ semantics.
C++ really does need language-level thread
support, because there are so many language-level
thread issues, of which this is only one. Yes,
you can do everything with OS calls, but the
language isn't giving you any help in getting it
right. Are there any good designs for this being
proposed?
John Nagle
Animats
---
[ 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: John Nagle <nagle@animats.com>
Date: Sat, 6 Apr 2002 16:26:35 GMT Raw View
Garry Lancaster wrote:
> David Abrahams <david.abrahams@rcn.com> wrote in message
> news:a87tpo$ipq$1@bob.news.rcn.net...
>
>>"Garry Lancaster" <glancaster@ntlworld.com> wrote in message
>>news:cxpo8.1489$3h5.729134@news11-gui.server.ntli.net...
>>
>>>Daniel Miller:
>>>[snip]
>>>
>>>>Deducing from your always-use-assert-macro-
>>>>instead-of-throwing-logic-error claim,
>>>>
>
> Garry Lancaster:
>
>>>Ahem. I didn't write that. I wrote:
>>>
>>>"Once you accept the wisdom of programming with
>>>the exception safety guarantees you can no longer
>>>accept that it's generally acceptable to throw an
>>>exception when class invariants are violated. (This
>>>is the sort of thing assertions are for, IMHO.)"y
>>it's formulated.
If an invariant is violated, the object is
broken. So what do you do then? Here are the main
options.
- Exit the program. Simple, but not always appropriate.
- Mark the object as broken, and throw on all future entries to
the object, other than the destructor.
Sound, but not well supported in C++.
No standard way to mark an object as "broken".
No simple way to enforce a "broken" flag
Destructor can't assume invariant is true.
- Throw, leaving object with an invalid invariant, and
rely on caller to delete.
Probably most common solution today.
Object is relying on its caller for its own
integrity, which is unsound.
That summarizes where we are, I think.
As usual, the most sound solution is bulky to write
in C++.
John Nagle
Animats
---
[ 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 ]