$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
From: William Kempf (williamkempf_at_[hidden])
Date: 2001-08-17 08:05:14
We're nearing the date slotted for submission of Boost.Threads, but there 
are still a couple of design issues that I need to work out.  So, I'm coming 
here one final time for opinions.  I *think* I know what to do in each case, 
but I want to see if I'm missing something.
The first is the thread specific storage concept.  My final design is the 
following:
template <typename T>
class thread_specific_ptr : private noncopyable
{
public:
    thread_specific_ptr();
    T* get() const;
    T* operator->() const;
    T& operator*() const;
    T* release();
    void reset(T* p=0);
};
This is similar to std::auto_ptr<> in it's interface, though the type is 
non-copyable.  When a thread is created it's thread specific instance is 
initialized to 0, so the first time get() is called it will return 0.  For 
this reason calling operator->() and operator*() may be dangerous.  Given 
this and the fact that these operations are expensive enough that most 
usages will probably use get() instead and cache the value, I'm wondering if 
the smart pointer operations should even be included?
The release() and reset() methods allow the programmer to decide whether or 
not thread specific data is stored on the heap or not since the following 
pattern can be used (which could be wrapped in a RAII class):
   static thread_specific_ptr<thread_specific_data> tsp;
   void my_thread_func()
   {
      thread_specific_data data;
      tsp.reset(&data);
      try
      {
         // thread functionality
      }
      catch (...)
      {
         // In case it's the theoretical thread_cancellation exception
         tsp.release();
      }
      tsp.release();
   }
So, this actually achieves all of the goals I discussed in an earlier 
posting, though this interface is decidedly different from the usual C APIs, 
and even of the "Thread Specific Proxy" pattern typically employed in C++.
The next design decision is the semantics for thread::join().  Alexander 
Terekhov had a lot of enlightening things to say in 
http://groups.yahoo.com/group/boost/message/16024.  I'll quote him in my 
discussion here.
(Alexander quoting someone else) "In fact, nobody could ever construct a 
case where it made sense for both threads 1 and 2 to wait for thread 3 to 
complete. While there might be cases where thread 3 was doing something on 
which two threads depended, joining with it is never the right (or best) way 
to implement that dependency. (It really depends on some DATA or STATE, not 
on the completion of the thread, per se.) And it's silly to require two 
calls to "finalize" thread 3. Furthermore, one of the most common DCE thread 
programming errors was failure to detach after joining, resulting in a 
memory leak."
In Win32 MT programming I've frequently waited on a thread handle multiple 
times.  Naively, I assumed this was functionality that was needed by 
Boost.Threads because of this experience.  However, once pointed out I must 
agree... these were all cases in which I was more interested in some state 
rather in the actual "full termination" of the thread, including cleanup 
procedures.  There's only ever one thread (usually the main thread of 
execution) that must wait for "full termination" in order to insure all 
cleanup occurs.  The other cases could more effectively be handled with 
condition variables instead of a call to join().  Further, since allowing 
multiple calls to join() from a pthreads implementation is going to be very 
problematic I'm now leaning towards following a slightly more POSIX stance 
on this.  (Not too surprising... POSIX *is* a full standard and as such 
great attention has been paid to some of these details.)
"so taking into account that "detach" applied to already terminated thread 
("DCE-joined" thread) is nothing else but simply a request to reclaim 
resources (thread id/native thread object), i would strongly suggest that it 
should be done automatically on destruction of C++ thread object which 
refers to not detached/"joinable" thread (after waiting until the target is 
complete if it was not already done "manually" using some thread::XXXX 
method(s)). in the case of detached thread, C++ thread object should be 
destructed implicitly on thread termination (inside thread bootstrap routine 
or via tsd destruction mechanism) followed by a request to reclaim thread 
native resources (id/native object) using pthread_detach( pthread_self() );"
This I don't agree with.  The join() method is a cancellation point (or will 
be if/when we support cancellation) and so could erroneously result in 
program termination if called during stack unwinding caused by another 
exception being thrown.  This one can't be worked around by disabling 
cancellation in the destructor as other cancellations can be.  I'd prefer to 
leave join() as an explicit operation.  The destructor shall either detach 
the thread if it is still "joinable", or do nothing if it's been joined.  
This also means that timed_join() and try_join() must go, since POSIX 
doesn't support such a concept and there's no way to extend Boost.Threads 
to.  Further, it means that I'm even more adamant that the name remains 
join() and not await_completion() because there are some semantics involved 
here that go beyond simply waiting for the thread to terminate.  I think 
Alexander tried to point this out a few other times but I didn't comprehend 
what he meant until now.  A call to join() not only waits for the thread to 
complete, but also performs any cleanup of implementation state required, 
including returning the "thread id" to the system for reuse.
So, I'd like to modify the thread concept in the following ways:
Constructors
thread();
Effects: Constructs a thread object for the current thread of execution.
Notes: *this is not joinable.
thread(const boost::function0<void>& threadfunc);
Effects: Starts a new thread of execution. Copies threadfunc (which in turn 
copies the function object wrapped by threadfunc) to an internal location 
which persists for the lifetime of the new thread of execution. Calls 
operator() on the copy of the threadfunc function object in the new thread 
of execution.
Throws: boost::thread_resource_error if a new thread of execution cannot be 
started.
Notes: *this is joinable.
Join
void join();
Requires: *this != thread() and *this is joinable.
Effects: The current thread of execution blocks until the initial function 
of the thread of execution represented by *this finishes. Insures all 
resources used by the thread have been reclaimed.
Notes: *this is no longer joinable.  After return no thread objects 
referring to the same thread of execution are valid any longer, and the only 
operation that's well defined for any of them is destruction.
Comments?
Bill Kempf
_________________________________________________________________
Get your FREE download of MSN Explorer at http://explorer.msn.com/intl.asp