[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