[cpp-threads] Re: Thread API interface tweaks

Howard Hinnant hinnant at twcny.rr.com
Sun Aug 27 19:17:48 BST 2006


On Aug 27, 2006, at 1:19 PM, Kevlin Henney wrote:

>> class thread_id
>> {
>> public:
>>    thread_id();                   // not a thread_id
>>    static thread_id current();    // current thread_id
>>
>>    template <class R> thread_id(const thread<R>& j);  //get id of  
>> thread
>>    pthread_t native() const;  // return type implementation defined
>>
>>    friend bool operator==(const thread_id& x, const thread_id& y);
>>    friend bool operator!=(const thread_id& x, const thread_id& y);
>>
>>    static void yield();                          // operate on  
>> this thread
>>    static void sleep(const timespec& rel_time);  // operate on  
>> this thread
>>    static unsigned n_cpus();  // number of cpu's, valuable for  
>> building thread pools
>> };
>>
>> thread_id is simply a way to store the id of a thread.  One can  
>> construct a thread_id from a joinable "thread".
>
> In my original design, the idea of a thread ID was more opaque and  
> was guaranteed only to support == and !=, although the idea was  
> that it could in principle offer a platform backdoor. I think it is  
> a good idea to formalise it as you have, but I think that it is  
> perhaps doing too much work, ie it seems to embrace multiple  
> responsibilities.
>
> There is the idea of a thread ID and then there is the idea of  
> yield, sleep, etc. There is no reason that they should be related  
> in the same class, and some good reasons not to. These current- 
> thread properties and manipulators would probably be better off as  
> non-member functions (perhaps nested in a namespace) as they are  
> not operations on ID or a way of getting low-level access to an ID.  
> Does that sound reasonable?

<nod> I'm not happy with thread_id::yield() either.  I had originally  
put these in joiner (now thread), which is also where boost has  
them.  Perhaps a tiny nested namespace is the best place for them.   
Got a good name for it?

std::thread_self::yield();

?

>> template <class Return>
>> class future
>> {
>> public:
>>    future(const thread<Return>& j);
>>    future(const future& f);
>>    future& operator=(const future& f);
>>    ~future();
>>
>>    bool is_done() const;       // thread has completed (perhaps  
>> abnormally)
>>    bool is_normal() const;     // A return value is available
>>    Return operator()() const;  // throws if is_done() && !is_normal 
>> ().
>> };                              //    Blocks if !is_done().
>>
>> The difference between a future and a thread is that the future   
>> refers only to the return value and not to the thread of  
>> execution.   One can not get a thread_id of a future.  The future  
>> is copyable,  where the thread is move-only.  The future  
>> destructor does not detach  the thread of execution as the thread  
>> destructor does.  The future  can block on the return value if it  
>> is not yet available, but it is  signaled via mutex/condition  
>> variables upon thread completion instead  of calling something  
>> like pthread_join.  There are observers to tell  if the thread has  
>> completed, and if so, whether it was a normal or  abnormal  
>> termination.  A value can be extracted repeatedly from the  same  
>> future.  In contrast a value can only be extracted once from a   
>> thread (after which the thread no longer refers to a joinable  
>> thread,  or to a return value).
>>
>> This design means that a future only keeps the return-value struct  
>> alive.  The thread of execution, and all of its associated OS  
>> resources are cleaned up on thread join, or upon termination of a  
>> detached thread.
>
> This makes a lot of sense.
>
>> Although a thread<void> is quite functional, a future<void> is  
>> senseless and should not compile (diagnostic required).
>
> However, I disagree with this. A future<void> still represents a  
> synchronisation mechanism and permitting avoids making an exception  
> that will undoubtedly trip up some generic code somewhere -- and  
> therefore cause us to repeal the omission of future<void> :-)

So given:

future<void> f1 = launch_thread(f);

What does this do:

f1();

?

It could act as nothing more than a signaling mechanism that the  
*detached* thread has ended.  However this seems like nothing more  
than a far more expensive way to say:

thread<void> t1 = launch_thread(f);
...
t1();  // calls pthread_join under the covers

Giving the signaling functionality to future<void> extracts a cost on  
thread<void> which I'm not sure I'm sufficiently motivated to pay  
for.  Perhaps it would be good to expose some implementation in this  
discussion:

template <class Return>
class thread
{
private:
     pthread_t id_;
     return_value<Return>* ret_;
...
};

The ret_ is essentially an intrusive_ptr to the return value stored  
on the heap.  But for thread<void>:

template <>
class thread<void>
{
private:
     pthread_t id_;  // only data member
...
};

I.e. you can launch a thread<void> with no heap allocation (and I'm  
very big on avoiding heap allocations :-) ).  But if we give  
future<void> meaning, I fear we will need to create a  
return_value<void> on the heap from the thread start function just in  
case a future<void> gets created at a later time that needs to  
receive a signal from it.

If it turns out that there's no way to avoid heap allocation when  
launching a void-returning thread anyway, then the cost of  
future<void> is probably negligible.  But I would like to try hard to  
keep a void-returning thread heap-free.

The sizeof(thread<void>) may also become an important factor for  
clients creating thread pools (containers of threads).  On Mac OS X,  
sizeof(pthread_t) is only 4.  So adding an extra pointer to  
thread<void>, even if it were to remain null in the absence of  
futures, would double the sizeof(thread<void>).

> Anyway, overall this looks great!

Thanks,
Howard




More information about the cpp-threads mailing list