Boost logo

Boost :

From: Peter Dimov (pdimov_at_[hidden])
Date: 2001-08-31 05:08:04


From: "William Kempf" <williamkempf_at_[hidden]>
> >You need to remember whether 'join' was called anyway (for the
destructor.)
> >So you can easily avoid the undefined behavior.
>
> Only if you retain the strict rules about adoption.

I stated about three times that this will be, indeed, the case, and adopted
threads will still invoke undefined behavior when joined.

> I realize that you want
> to make adoption fully undefined behavior,

This is not being discussed in this subthread. ;-) In this subthread,
adoption works as you've defined it to work. Fifth time: adopted threads
invoke undefined behavior when joined.

> It's not really a general question. Obviously all operations on the
thread
> object must be thread safe (and they will be). The only one that can be
> questionable is "join", but this operation is thread safe simply because
> it's undefined behavior to call it more than once.

Exactly. Obviously all operations on the thread are thread safe, but join is
not. I don't see any specific reasons for this inconsistency.

> >> >case 3: access serialized, subsequent joins have no effect. Well
defined
> >> >behavior. Both threads join x.]
> >>
> >>Actually, only one thread "joins x", by very definition of "join" ;).
> >
> >The definition of join is:
> >
> >Effects: The current thread of execution blocks until the initial
function
> >of the thread of execution represented by *this finishes and all
resources
> >are reclaimed.
> >
> >Postcondition: *this is non-joinable.
> >
> >Both threads join x, by this definition. You can't detect from a
conforming
> >program how many threads joined x simultaneously. (Ignoring the
> >non-joinable
> >vs adopted-non-joinable issue for now.)
>
> OK, we need to make the Boost.Threads documentation more clear here. The
> term "join" differs from "wait" in that a "join" actually reclaims the
> thread resources mentioned in the above definition. Only some of the
thread
> resources are reclaimed when the thread ends (implementation defined), the
> rest are reclaimed only when "join" is called or the thread is "detached"
by
> the destructor (and the thread has ended). You can't "join" twice because
> this act reclaims the thread resources and you can reclaim the resources
> only once.

Join doesn't reclaim resources by your definition -- which is correct, in my
opinion. Join blocks until resources have been reclaimed, which is not the
same thing -- and allows you to fully implement thread safe joins. (The
resources would have been reclaimed even without a join, so it's not 'join'
that reclaims.)

We're in the context of case 3 here. Join is serialized. The second join is
a no-op, which conforms, since the resources have already been reclaimed by
the first join. Both joins satisfy the definition. When they return, the
resources have been reclaimed.

> As for the assertion that "you can't detect from a conforming program how
> many threads joined x simultaneously", this is simply not true, since a
> conforming program CAN'T call "join" more than once, no matter what
> thread(s) it's called from.

We're in the context of case 3. A conforming program can call join mutliple
times, from different threads, and every single call to join conforms to
your definition.

> >I don't see why this complicates thread adoption; it's a separate issue.
> >Joining an adopted thread will invoke undefined behavior, as usual.
>
> In a conforming program you can not tell if a thread is "adopted" or not
> when you call "current" (or in our case the default constructor).

Your current design always adopts on default construction, does it not? Even
for threads that've been created by the library. I don't see the issue.
Calling 'join' on a default-constructed thread object is undefined behavior,
period.

You could even go further and separate the two thread classes, avoiding the
undefined behavior altogether. boost::adopted_thread will simply not have a
join member.

> >As for the overhead... what overhead? Join blocks for an indeterminate
> >amount of time anyway; and the second join is undefined behavior by the
> >current specification so there's no added overhead here either.
>
> If a second "join" is undefined behavior, like it is in the submission,
then
> there is no overhead. You were arguing for a second "join" to be, in
> effect, a no-op instead of undefined behavior, and this would add overhead
> since you'll have to synchronize access to some state that indicates
whether
> the thread has been joined already or not. This means additional state
data
> (memory/resource overhead) as well as making the (indeterminate) blocking
> time even longer for the initial call to "join".

What a terrible overhead would that be. Definitely unacceptable. ;-)

On a more serious note, you can't avoid the synchronization objects for the
state when you expand the design, and the time overhead for the first join
will be unmeasurable.

This is a general problem with these discussions. We have something (the
current design) and we try to talk about it but we also try to foresee how
it'd look in the future when the additional functionality is added, and
everyone sees slightly different things.

--
Peter Dimov
Multi Media Ltd.

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk