|
Boost : |
From: terekhov (terekhov_at_[hidden])
Date: 2002-01-24 11:39:04
[...]
> > May I just repost
> > the last message I've sent to you yesterday (you did not reply
> > yet)?
>
> Feel free to. I haven't replied because there wasn't much new that
I
> felt required replies, and I'm hip deep in other things :).
To: "William Kempf" <williamkempf_at_[hidden]>
cc: bdawes_at_[hidden], pdimov_at_[hidden]
From: Alexander Terekhov/Germany/IBM_at_IBMDE
Subject: Re: Boost.Thread/thread_ptr ?!
I wrote:
> if ( 0 == thread::current() )
> thread::adopt_current(); // creates joinable_thread< void* >
>
> // OK
> thread::current()->exit();
>
> // Throws bad_cast
> thread::current()->exit( "OOOPS" );
That is an error. This behavior should (IMHO) apply
to main/initial thread and any other C++ thread with
*void* thread routine.
I think that any other adopted thread should be
allowed to commit suicide returning anything
via void* ptr on thread::current()->exit(blahbla)
(just like standard PTHREADS thread routines; BTW,
since they are joinable by default, I think that
we should NOT support adoption of detached threads,
though it is possible to implement and even throw
bad_cast manually on attempt to promote/create a
joinable thread pointer)
regards,
alexander.
---------------------- Forwarded by Alexander Terekhov/Germany/IBM on
01/23/2002 07:52 PM ---------------------------
To: "William Kempf" <williamkempf_at_[hidden]>
cc: bdawes_at_[hidden], pdimov_at_[hidden]
From: Alexander Terekhov/Germany/IBM_at_IBMDE
Subject: Re: Boost.Thread/thread_ptr ?!
> Helpful summary. Let me address my own opinion of these 3.
Oh. I forgot one more:
4) returning (pthread_exit()ing) address of local variable.
> 3) I honestly don't understand this one.
http://groups.google.com/groups?as_umsgid=3C47EF76.C676B015%40web.de
> 1) Returning a pointer from a function invariably leads to the very
bad
> mistake of returning a pointer to an object on the stack which
won't exist
> after the function returns, dropping us into undefined behavior
land.
> What's really bad here is that on many platforms such a mistake can
go
> uncaught since it appears to work.
Function does not return pointer (well, it could, but that is NOT
necessarily), if it returns a non-ptr type (some object) it is
just automatically stored (perhaps sort of placement new) in
joinable_result_holding_thread<> thread object. joinable_thread<>
is a base class of joinable_result_holding_thread<> and it holds
a pointer to a supper class member (or PTHREAD_CANCELED), which
is set on first join oper/pthread_join. Everything is done
using POSIX's pthread_join(). joinable_thread<> just knows the
right type of returned object ptr. For void functions or
functions returning pointers joinable_result_holding_thread<>
is not used at all. Any way, the user never sees
joinable_result_holding_thread<>. It should be hidden.
Everything is done by new_thread( function,{arg....}) factories
and thread_ptr/current_thread_ptr/joinable_thread_ptr< t >
pointers. Normally user does not even see thread objects.
I think, however, that it would be nice to provide
an option to use user-defined "thread" subclasses
instead of "thread"-always. Perhaps a default (thread_type)
template argument for ptrs/factories would work...
> 2) The vast majority of the time there's no need for a return value
at all.
> Requiring a return value in the function signature then leads to the
> annoyance of having to provide a bogus return value in these cases.
Agree, therefore:
void f();
...
{ new_thread( f ); }
DOES LOOK GREAT! Well, at least to me ;) It will even detach
on thread termination with no other pointers (e.g. published by f())
left!!
> 3) When you do need to return information from a thread there's
already a
> logical place to put the data... in the function object used to
create the
> thread.
Yep. Note that joinable_result_holding_thread<> just combines
everything needed for non-ptr/non-void returning functions.
> If there truly is a need to determine if a thread was cancelled and
> explicitly coding for that using (3) above is considered too much
work then
> maybe we should return a simple bool only, which would be implicitly
> returned?
But what do you do with the result (lack of it)? adding a second get
()/
result() method and defining a precondition (joined¬-canceled)
is not really the way I like. POSIX does it with a special pointer
and I really like this way, IMHO.
> Most "polymorphic" solutions for this are too expensive and
generally create
> some problems for the users. The Boost.Bind library may be
complicatd in
> it's implementation because it avoids this, but its usage is
straightforward
> and simple. You shouldn't care about the implementation, only
about how
> easy and safe it is to use. More importantly, I think it's a bad
idea to
> "reinvent the wheel" for a concept such as this in a threading
library.
Yes, new_thread DOES have a lot in common with bind. So what? Why not
even share implementation details or even somehow use bind under the
cover.
> You didn't like the semantics of the original boost::thread in this
regard.
> The only differences here that I see are that you use a seperate
type and
> you allow promotion to a shareable type.
Yep. shareable/joinable type. ;-)
> >// This one is basically encapsulating "my_thread_t"
> >// but does NOT allow join operations
> >class thread;
>
> Then does it not "leak"? I don't see how/why this is used in your
> descriptions.
See mythread_t impl... (if you mean thread leakage)
> I thought you wanted an implementation that was closer to POSIX.
Yes, originally I did. That was a mistake. I am sorry.
Peter & repeated messages on c.p.t. did convince me that
ref.counting is the only right way to go. After all,
"experts" (not me) could always use C-pthreads API with
all of its "C" error-prone nature.
> This description points out what I like the least about all of
this. There
> are too many variants for a single concept (thread), all of which
behave
> very differently but can be used to represent the same thread of
execution
> (at the same time!). This is just going to lead to confusion, and
a lot of
> "newbie" mistakes.
Well, newbies won't see/use all of these classes. And after all,
// thread_function return thread-safe shared_ptr< my_object >
thread_shared_ptr< my_object > locate_object();
// Create thread, do not care about "join"
thread_ptr tptr = new_thread( locate_object );
...
// Oh. I want to join now.... could throw bad_cast
joinable_thread_ptr< thread_shared_ptr< my_object > > jtptr( tptr );
// Now safely join and use thread_shared_ptr< my_object >*
join_result.
// No cancel expected.
jtptr->join()->my_object_do_something();
// Again join.. useless, but OK
jtptr->join()->my_object_do_something_else();
IS NOT THAT MUCH HARD/COMPLICATED! (<- these are just BOLD letters,
NOT shouting ;-) IMHO.
And, BTW, if you never join, you just do not use any
joinable_thread_ptrs at all! Everything will be detached
automatically then. But you could always change your mind
and promoted/create joinable ptr for {try/timed}join() opers.
You just have to know the thread_routine's result type...
and it will throw bad_cast if you would try to cheat ;-)
> I'd agree, but you don't discuss *HOW* to adopt such a thread
explicitly.
if ( 0 == thread::current() )
thread::adopt_current(); // creates joinable_thread< void* >
// OK
thread::current()->exit();
// Throws bad_cast
thread::current()->exit( "OOOPS" );
> Unless main() is supplied by the library the above is simply too
complex.
> If main() is supplied it makes it more difficult to add some
threads to an
> existing application (and may make it impossible to do in shared
libraries)
Well, usually vendors provide sort of "lib-init" function(s).
> and results in rather unusual coding practices. I'd rather leave
things as
> they are in POSIX when it comes to the "pthread_exit()" problem
I hope that someday it will become thread::current()->exit()
problem. ;-)
---- UUUU.. tired. hope I did not miss something important among the points you have identified. Anyway, thank a lot Bill, for your review and taking the time! Personally, I would never even look at such poorly prepared "document" which I've thrown on you! ;-) regards, alexander. To: Alexander Terekhov/Germany/IBM_at_IBMDE, bdawes_at_[hidden], pdimov_at_[hidden] cc: Subject: Re: Boost.Thread/thread_ptr ?! >< in some other long and rather frustrating thread Beman wrote: > > > > ...back onto more productive ground. Please help! > >Yes, sir ;) > >Below is the stuff from my "sketch-pad" related to C++ >front-end to the "mythread_t" I've already presented >to you privately and later, on the boost list as well, >as a link to my private web site. > >Basically, from my year long or so presence on c.p.t, >I could testify that the most frequent threading >errors are: > >1. failure to detach/join threads; >2. failure to call pthread_exit() from main or > just join relevant threads prior to invoking > process termination; >3. assumption that a pthread_t written by > pthread_create is visible to newly created > thread. Helpful summary. Let me address my own opinion of these 3. 1) This is no different then the failure to delete a pointer. A real issue, and one for which a solution is warranted, but no solution is optimal. For example, look at the number of smart pointer variants there are to deal with this issue. So I don't think it's right for the boost::thread class to address this problem. I think it's the domain of higher level concepts to deal with this. 2) I'll have to look at how you've addressed this one, but I think it's not really any different than (1). Most of the solutions I can think of have a cost that I'd consider too high. In other words they would prevent me from paying for only what I need. 3) I honestly don't understand this one. >I think that the "design" roughly sketched >below does address all of these problems >plus provides typesafe C++ equivalents of >"exit( result )/result* {try/timed}join()". > >Please have a look at it and feel free >to do with it whatever you think it deserves, >starting from filling your dev/null and >up to perhaps asking more questions ;-) > >regards, >alexander. > > >---- > > > ...but why do joins return result* and not result? > >That is basically to: > >- provide canceled/no-result indication > (returning e.g. static joinable_thread< typename result > result* > joinable_thread< result >::canceled() ptr or static template class > ptr constant instead of static function, similar to PTHREAD's > PTHREAD_CANCELED non-NULL unique ptr) > >- provide tryjoin-failed/busy/no-result indication > (returning e.g. static joinable_thread< typename result > result* > joinable_thread< result >::busy() ptr or static template class ptr > constant instead of static function, similar to PTHREAD's > PTHREAD_CANCELED non-NULL unique ptr) > >- provide timedjoin-failed/join_timedout/no-result indication > (returning e.g. static joinable_thread< typename result > result* > joinable_thread< result >::join_timedout() ptr or static template > class ptr constant instead of static function, similar to PTHREAD's > PTHREAD_CANCELED non-NULL unique ptr) I disagree with this decision. Here's my reasoning: 1) Returning a pointer from a function invariably leads to the very bad mistake of returning a pointer to an object on the stack which won't exist after the function returns, dropping us into undefined behavior land. What's really bad here is that on many platforms such a mistake can go uncaught since it appears to work. 2) The vast majority of the time there's no need for a return value at all. Requiring a return value in the function signature then leads to the annoyance of having to provide a bogus return value in these cases. 3) When you do need to return information from a thread there's already a logical place to put the data... in the function object used to create the thread. 4) To really use the pointer return concept correctly you wind up using the equivalent of (3) any way. If there truly is a need to determine if a thread was cancelled and explicitly coding for that using (3) above is considered too much work then maybe we should return a simple bool only, which would be implicitly returned? >(note that user's thread function/routine itself may not >necessarily return PTR-type, it could be type/void) > >Below is the newer version from my "sketch-pad". >Hopefully it will clarify things a bit more (instead >of confusing you totally ;) > >Basically the idea is to use both runtime-polymorphism/ >dynamic_casts for typesafe exit()/{..}join() AND >complile-time meta-stuff. I've got an impression >that "thread_new" is pretty much the same (but w/o >_? stuff) as Peter's "bind"er already is (which drives >me crazy when I see how complicated its impl is for >a such meta-impl-newbie as I am ;) Most "polymorphic" solutions for this are too expensive and generally create some problems for the users. The Boost.Bind library may be complicatd in it's implementation because it avoids this, but its usage is straightforward and simple. You shouldn't care about the implementation, only about how easy and safe it is to use. More importantly, I think it's a bad idea to "reinvent the wheel" for a concept such as this in a threading library. >// This one is basically encapsulating "my_thread_t" >// but does NOT allow join operations >class thread; Then does it not "leak"? I don't see how/why this is used in your descriptions. >// Holds a ptr to thread object and calls addref()/unref() >// COULD hold null pointer >class thread_ptr; I thought you wanted an implementation that was closer to POSIX. POSIX doesn't ref-count, and I think that was the right decision. I don't have anything against there being a ref-counted variant, but the description presented here shows that this is actually *THE* variant actually used, with no non-ref-counted variant that can be used when creating threads (the only non-ref-counted variant is not thread shareable and can only be created for the current thread of execution). >// Result of thread::current - does not provide ref.counting >// (for better performance) and is convertable to thread_ptr >// and joinable_thread_ptr<result>/joinable_result_holding_thread<result> >// *using dynamic_cast* This description points out what I like the least about all of this. There are too many variants for a single concept (thread), all of which behave very differently but can be used to represent the same thread of execution (at the same time!). This is just going to lead to confusion, and a lot of "newbie" mistakes. >// ------- >// Provides template< class result > void exit( result ); >// which does dynamic_cast under the cover >// ------- >// thread::current() *returns NULL-PTR for non-adopted/"non-C++" threads* Then how do you "adopt" the thread? >// ------- >// valid in a calling thread ONLY/non-thread-shareable w/o >// construction of some other type of ref.counted thread pointer >// ------- >class current_thread_ptr; You didn't like the semantics of the original boost::thread in this regard. The only differences here that I see are that you use a seperate type and you allow promotion to a shareable type. Using different types at first seems to make the differing semantics obvious and so would be considered a good thing, but the existing of different types (especially when they are convertable to each other) will only lead to confusion, IMHO. If we can remove the restriction on not being able to share the object entirely then you've got a much better solution. I think the latest design I posted does this, and gives us a design that's more faithful to POSIX. >// --- >// I was just thinking that to just store/compare different thread id/ptrs >// it would be too prohibitive to require them to be of the same type >// (according to type of thread_routine return value). For example, >// it would significantly complicate/invalidate code which currently stores >// and compares pthread_t objects. Basically thread_ptr is a C++ >"equivalent" >// of pthread_t (but with extra NULL state, lack of witch is a real >problem/ >// inconvenience in PTHREADS). Making thread::current() return thread_ptr >// instead of current_thread_ptr would mean extra mutex locking... might be >// OK, but given that I've used to FAST pthread_self(), personally, >// I have difficulties with this idea. On the other hand, I do not see it >// as a critical issue. >// --- >// (The idea of current_thread_ptr is based on my experience that >programmers >// thread-share pthread_t objects obtained from pthread_self() rather >rarely. And >// since "self" ref is already counted in the "thread state", it is ok NOT >to ref.count >// copies of it AS LONG as they are NOT shared with respect to other >threads. Correct as far as safety is concerned, but I still don't like the idea. >// >// Also, I do not like the idea of "automatic" adoption under the cover of >thread::current(), >// I would rather prefer that thread::current() would return an indication >(NULL-ptr) that >// running/current thread was not created as C++ thread/already adopted as >C++ thread, >// and would let ME decide what to do in such situation. I think that it is >more flexible. I'd agree, but you don't discuss *HOW* to adopt such a thread explicitly. Also, it complicates things if "current()" can return "null". The latest design I posted doesn't return "null", but instead returns a non- adopted thread, for which most operations can still be applied. More importantly, since this design is very close to POSIX, if adopted as a standard it should be possible for implementers to do all of this with out the need for adoption (and so I'd expect that the standard would define this concept with out adoption). >// Exception would also work, and actually, I am not sure what is better - >exception or >// NULL-ptr...) I'd say "null" is, but either would work. I just think the solution I posted addresses the issue in a cleaner way. >// Provides typesafe result* {try/timed}join() methods. >// Used for functions returning a pointer type only >template< class result > >class joinable_thread : public thread {}; > >// joinable_thread smart ptr >template< class function > >class joinable_thread_ptr : public thread_ptr {}; > >// Provides typesafe result* {try/timed}join() methods. >// Used for NON-ptr returning functions only (and void funcs), >// holds function result for joiners (or NULL-ptr for void >// funcs); >template< class result > >class joinable_result_holding_thread : public joinable_thread_ptr {}; > >// Used to create joinable_thread (for "type* f()") and >// joinable_joinable_result_holding_thread (for "type f()") >// objects >template< class function > >struct thread_traits; > >// Helper class >template< class function > >class new_thread_ptr : public joinable_thread_ptr< thread_traits< function > >::result > {}; And now we seem to have about 6(?) different "thread" types with no clear distinction of when to use which. >// Thread factory for functions with no arguments >template< class function > >new_thread_ptr< function > new_thread( function ); > >// Thread factory for functions with ONE argument >template< class function,class arg > >new_thread_ptr< function > new_thread( function,arg ); > >// etc.. I think it's much cleaner to use a concept such as Boost.Bind here. >// Example > >int* f1() { /***/thread::current()->exit( some_int_ptr ); } >int* f2(int) { /***/thread::current()->exit( some_int_ptr ); } >int f3() { /***/thread::current()->exit( blah+blah ); } >int f4(int) { /***/thread::current()->exit( blah+blah ); } >... >// No cancel expected >return *new_thread( f1 )->join() + > *new_thread( f2,10 )->join() + > *new_thread( f3 )->join() + > *new_thread( f4,10 )->join(); > >---- >string fs() { /***/thread::current()->exit( string("hello word") ); } > >// Cancel checking >joinable_thread_ptr<string> tptr = new_thread( fs ); // >joinable_result_holding_thread >// joinable_thread_ptr<thread_traits( fs )::join_result> tptr = new_thread >( fs ); > >//... >tptr->cancel(); >string* presult = tptr->join(); > >cout << ( tptr->canceled() == presult ) ? "CANCELED" : *presult; > >---- >string fs() { /***/thread::current()->exit( string("hello word") ); } > >// try join/no cancel expected >joinable_thread_ptr<string> tptr = new_thread( fs ); // >joinable_result_holding_thread >// joinable_thread_ptr<thread_traits( fs )::join_result> tptr = new_thread >( fs ); >//... >string* presult = tptr->tryjoin(); > >cout << ( tptr->busy() == presult ) ? "BUSY" : *presult; > >---- >string fs() { /***/thread::current()->exit( string("hello word") ); } > >// timed join/no cancel expected >joinable_thread_ptr<string> tptr = new_thread( fs ); // >joinable_result_holding_thread >// joinable_thread_ptr<thread_traits( fs )::join_result> tptr = new_thread >( fs ); >//... >string* presult = tptr->timedjoin( timeout ); > >cout << ( tptr->join_timedout() == presult ) ? "JOIN TIMEDOUT" : *presult; > >----- >// user's main >void thread::main( blabla ) >{ > > // Exit main/initial thread and wait for last thread term. > // The same is done automatically on return from this func. > thread::current()->exit(); // checks the type of currect thread > // object and essentially just calls pthread_exit( NULL ); > > // Process exit should be done explicitly: > // exit( 0 ); > >} > >// lib's main >int main( blabla ) >{ > > // Adopt main thread/create joinable thread object for *void* func > //.... > > // Resume "forced-suspended" global threads > //.... > > // Call user's main() > thread::main( blabla ); > > // Exit main thread > pthread_exit( NULL ); > >} Unless main() is supplied by the library the above is simply too complex. If main() is supplied it makes it more difficult to add some threads to an existing application (and may make it impossible to do in shared libraries) and results in rather unusual coding practices. I'd rather leave things as they are in POSIX when it comes to the "pthread_exit()" problem (i.e. the user must explicitly call it if there's any chance there are other threads still running) and when it comes time to standardize this we can ask for a core language change here to remove the need entirely. Bill Kempf williamkempf_at_[hidden] _________________________________________________________________ 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