Boost logo

Boost :

From: William Kempf (williamkempf_at_[hidden])
Date: 2001-08-30 14:44:19


From: "Peter Dimov" <pdimov_at_[hidden]>
>From: "William Kempf" <williamkempf_at_[hidden]>
>>From: "Peter Dimov" <pdimov_at_[hidden]>
>> >The main thread is significantly different from other 'adopted' threads
>> >assuming that such threads even exist. This is why it deserves special
>> >treatment.
>> >
>> >Every program has a main thread. Many (most?) programs don't have
>>adopted
>> >threads.
>>
>>I understand the reasoning and the concern. The problem is, the
difference
>>is a very low level detail, part of the abstract machine, and the higher
>>level libraries (POSIX and Win32) we have access to (rightfully) hide the
>>differences and give us no way to differentiate the "initial thread" from
>>any other. So, from an implementation stand point for Boost.Threads,
there
>>can be no difference. I expect the standards folks to address this issue,
>>however.
>
>I don't think that the abstract machine has anything to do with it. The
>main
>thread is created by the runtime, whereas additional threads are explicitly
>created by user code via the native threading primitives.

The "runtime" is a part of the abstract machine.

>Any dynamically initialized object with static storage duration will have
>its initialization expression/constructor executed by the initial thread,
>AFAICS. So the initial thread is very much detectable and 'special',
>without
>any modifications to the abstract machine.

Ahh... but here's the rub. You can't statically initialize a thread
identifier. In pthreads you have to call pthread_self() and in Win32 you
have to call GetCurrentThread() in order to get the "current" thread
identifier, and the minute you do this you're no longer in static storage
duration. When such data is initialized follows specific rules (which I
know you're familiar with) and these rules do not gaurantee that they'll be
initialized by the "initial thread", and in fact under both Win32 and POSIX
you have no gaurantee what thread will initialize such data.

>>We can still safely assume that "whoever created the thread will join or
>>detach it". There's no requirement in either POSIX or Win32 for user code
>>to "join" or "detach" the "initial thread". The abstract machine takes
>>care
>>of this for you. You are right, however, that for both it's possible to
>>specifically "join" the "initial thread", and Boost.Threads has lost this
>>ability.
>
>Exactly.

But, again, I don't see this as much of a loss. I strongly feel that it's a
design error to do this, and if you absolutely must do this you can simulate
it in user code any way.

>>I don't know how to safely and acceptably allow this, and find it
>>to be an acceptable loss. I really do feel that "joining" the "initial
>>thread" is a design error, and if one insists on such a design they can
>>accomplish it by having a "thread_main" that's the only thread spawned by
>>main().
>
>True for user code; unfortunately not an option for a thread_ref wrapper
>over boost::thread.

Why does a thread_ref wrapper need to "join" main? It's still going to be a
"design" mistake to do so, IMHO, and a "thread_main" approach will still
work if it's needed.

void thread_main()
{
// actual application entry point
}

int main()
{
   thread_ref ref = create(&thread_main);
   join(ref);
}

It's now nearly impossible for anyone to acquire a "reference" to the
"initial thread", and anyone that does had better not attempt joining it!
(The only way to get a "reference" is through a call to "current" called
during the initialization of global objects, and they have no business
attempting to "join" the reference they've obtained this way. This is a
very big part of the reason why I consider it a design flaw to ever "join"
the initial thread.)

>>I'm not arguing that the "initial thread" isn't a special case, I'm
arguing
>>that given the implementation constraints that Boost.Threads has, it can't
>>treat it as a special case. The "initial thread" simply must be treated
>>the
>>same as any other "adopted" thread.
>
>Given the quality of your current implementation, I very much believe that
>you can overcome the technical problems. ;-)

How. Seriously, show me the error of my ways and I'll gladly change the
design here in many ways. If you can safely and completely adopt main,
including the ability to "join" it, then I should be able to do the same
with EVERY adopted thread and we can open up the design constraints a LOT.

>> >Without additional support from the runtime, the only requirement on the
>> >user code is that the first call to create() or current() (or the first
>> >attempt to construct a boost::thread) is made from the initial thread.
>> >Which
>> >is quite reasonable.
>>
>>Well, I never thought of adopting the thread within create(). That does
>>make things seem to be "quite reasonable". Unfortunately, legacy libraries
>>can still cause holes in this if they spawn threads through native
routines
>>and these threads call into routines that call current(). I may be able
>>to
>>fill the holes in here...
>
>You can get very close, I believe. If the translation unit that contains
>current()/create() (or alternatively the boost::thread constructor) has a
>static object, its constructor is guaranteed to be executed before the
>first
>call to a function in this translation unit. On nearly every implementation
>this constructor will be executed by the initial thread, unless it's in a
>.so, and the .so is loaded with dlopen() from within another thread...
>which
>is a corner case.

The first call to current() is not helpful, because, as I pointed out, the
rules for initializing the global static object won't even begin to
gaurantee that it occurs in the initial thread (and .so/.dll semantics don't
effect this). This is why POSIX has pthread_once. Like I said, adopting
the thread in current() *seems* to fill in the holes, except that current()
may be called from an adopted thread before any calls are made to create().

>Besides, I don't ask for an implementation that works all the time. I ask
>for a specification that calls for an implementation that works.

I prefer an implementation that leaves as few holes as possible. I guess
you and I have different design goals in this regard. The design I've
submitted has no holes in any of these areas and thread adoption is
transparent and has no side effects. Yes, this required some strict usage
rules for user code, but I don't believe these restrictions to be too
"binding". The functionality you want can be implemented on top of this
design as long as you're willing to live with some added "undefined
behavior". (Again, the one issue is with "current" thread objects only
being valid within the current thread... you'll have to say if you want me
to lift this restriction or not. It's there only as an optimization for the
implementation.)

Bill Kempf

_________________________________________________________________
Get your FREE download of MSN Explorer at http://explorer.msn.com/intl.asp


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