[cpp-threads] Re: Thread API interface tweaks

Howard Hinnant hinnant at twcny.rr.com
Wed Aug 30 01:55:09 BST 2006


I'm about 24 hours behind in this thread, my apologies.  I'm posting  
back in part just to catch up and confirm my understanding...

On Aug 28, 2006, at 1:59 PM, Peter Dimov wrote:
> Here's my future<> sketchy implementation for reference:

Thanks Peter, that really helped.  I'm beginning to understand (I'm a  
slow learner :-)).  Here future<R> is truly a *non-intrusive* wrapper  
around thread<void>, unlike the partially intrusive wrapper I laid  
out.  I really like that it is completely non-intrusive.  This means  
it is not possible for it to impact the lower level thread launching  
efficiency.

I don't think your design specifies that thread can't return a type.   
It only specifies that future<R> builds on thread<void> instead of  
thread<R> like I had.  So *if* we want thread to be able to return  
non-void types (and I strongly suspect we might), we can still do  
that with your design.

Some downsides though:

The syntax for launching a thread, and launching a future are  
different, e.g.:

thread<int> t = launch_thread(g);

future<int> f = threaded_executor().execute(g);

The above is probably more different than it needs to be, but still,  
with Peter's (I think superior) proposal, at the very least we are  
looking at:

thread<int> t = launch_thread(g);
future<int> f = launch_future(g);

Peter's design adds an extra start-function wrapper around even what  
thread sees, necessitating a different launcher.  That appears key to  
having futures be non-intrusive.

If we're very concerned about this, we could possibly do away with  
factory functions/classes and put the launching functionality back  
into thread/future (as Beman has favored all along):

thread<int> t(g);
future<int> f(g);

The downside of the lack of factory functions/classes is the loss of  
being able to make OS-specific settings once, and use those settings  
to launch many threads (stack size, scheduling, priority, etc.).

Another effect is that future<R> is no longer constructible from a  
thread<R>:

thread<int> t = ...
future<int> f(t);  // fails at compile time.  Do we care?

I'm unsure of the importance of this conversion.

On Aug 29, 2006, at 8:43 AM, Nick Maclaren wrote:
> Well, I can think of a fair number of uses for void futures, so it
> would be more than just surprising if they are banned.

My chief motivation for avoiding future<void> was to keep from  
imposing an efficiency penalty on thread<void>.  With a completely  
non-intrusive implementation of future, my objection to future<void>  
wanes.

I note that this implementation of future does not expose a thread- 
id.  I think that is a good thing.  I can see where a shared (const)  
view of a thread's return value is valuable.  But I get nervous about  
shared ownership of a thread of execution where each owner has non- 
const access to that thread of execution (e.g. the ability to change  
a thread's priority).  As far as I can tell, Peter's solution is safe  
in this department.  This is analogous to our collectively-learned  
lesson that a reference counted design is good for an immutable  
string, but not good for a mutable string.

On Aug 29, 2006, at 1:25 PM, Herb Sutter wrote:
> So future<T> should be implicitly convertible to future<void> if  
> I'm not interested in the specific result

<nod> That's a very interesting conversion.  I prototyped it  
yesterday on my future's proposal after Beman pointed me in that  
direction.  I haven't prototyped it yet on Peter's proposal, but I  
suspect it won't be much trouble.

>   future_group<int> g1 = f1 || f2;
>   future_group<void> g3 = ( f1 || f2 ) && f3;

Very interesting syntax suggestions, thanks.

On Aug 29, 2006, at 5:00 PM, Lawrence Crowl wrote:
> The distinction between class thread and class thread_id seems  
> excessively
> complex.  Why should we have thread at all?  Why not just thread_id?
> The storage can be deallocated after completion of the join operation,
> which is essentially equivalent to new/delete.

The motivating use case for me came while trying to implement a  
recursive_mutex adaptor (similar in spirit to a std::stack or  
std::queue - container adaptors).  It looks something like:

template <class Mutex>
class recursive_mutex
{
private:
     Mutex& mut_;
     unsigned count_;
     thread_id id_;
public:
     explicit recursive_mutex(Mutex& mut)
         : mut_(mut), count_(0) {}  // thread_id default constructed  
to "not a thread"

     void lock()
     {
         thread_id self = thread_id::current();
         if (count_ > 0 && id_ == self)
             ++count_;
         else
         {
             mut_.lock();
             id_ = self;
             store_release(&count_, 1);
         }
     }
     ...
};

I.e. recursive_mutex<Mutex> takes any exclusive Mutex and turns it  
into a recursive mutex.  To implement this I needed the notion of  
thread id.  But thread, which can refer to exclusive ownership of a  
thread of execution, and which may need to contain more data than a  
simple id, is overkill for this application.

On Aug 29, 2006, at 5:00 PM, Lawrence Crowl wrote:
> Futures are a very valuable facility, too valuable to be tied to  
> threads.
> They should be a separate facility that happens to also work with  
> threads.

That sounds very interesting to me.  Got a prototype?  I'll start  
working, but I don't trust myself to correctly implement this  
suggestion.  It would be good to have more than one prototype to look  
at.

On Aug 29, 2006, at 5:00 PM, Lawrence Crowl wrote:
> We already have a keyword __thread, and I think that defining a type
> named thread will lead to confusion.  The joiner terminology at least
> does not have that problem.

Agreed.  Although I'm not as sure of the resolution.  We could rename  
either __thread or thread.  E.g. __thread_local.

-Howard




More information about the cpp-threads mailing list