[cpp-threads] Re: Thread API interface tweaks
Howard Hinnant
hinnant at twcny.rr.com
Wed Aug 30 23:03:24 BST 2006
On Aug 30, 2006, at 4:13 PM, Peter Dimov wrote:
This part:
> class handle;
> // CopyConstructible, Assignable
plus this part:
> // void cancel( handle th );
> // void set_cancel_state( handle th, bool st );
> // void test_cancel();
has me worried. And it is unfortunate that I highlighted "cancel" as
the example because of all the baggage it carries. What I'm really
concerned about are non-const operations on a copyable thread
handle. And I think whether or not we add cancel, people will need
and want to perform OS-specific non-const operations on a thread
handle (set priority for example). So cancel here is just an example
of a non-const operation. Ignore the precise semantics of cancel.
Consider:
handle h;
... lots of code ...
cancel(h);
// is h in the cancel state here?
...
// Has another thread changed the state of h out from under us yet?
If h is copyable, possibly to some other thread, the programmer can't
reason about the effect of non-const operations on the thread, unless
a mutex is wrapped (by him) around it, and religiously respected:
handle h
mutex lock_h;
...
lock_h.lock();
cancel(h);
// h is still canceled here
lock_h.unlock();
// other threads can now change h via a copy
On the other hand, if h isn't copyable (has unique ownership of the
thread of execution), none of that is necessary. It is now provable
that:
handle h;
... lots of code ...
cancel(h);
// h is still in the cancel state here
...
// and here
This seems so much less error prone to me that I find it extremely
compelling. Throw in that we can still make the thread-owner
movable, and thus can be returned from functions and put into
containers, and I'm just completely lacking the motivation to make
thread-owner copyable.
> I was actually a proponent of thread<R> once, so it's not a NIH
> syndrome; I just realized that the motivation for having thread<R>
> is simply that I was missing the future<R> component.
The nice thing that you can do with thread<R> that you can't do with
future<R> is:
thread<vector<int>> t = launch_thread(g);
...
std::vector<int> v = t(); // returned by *move*, not by *copy*
You can't move-return from a future because there might be another
future also wanting to get a copy.
If we make a rule that you can't view the return of a future<R>
twice, then we could say that:
std::vector<int> v = f(); // moves if refcount on return is 1
v = f(); // not legal
However even this can be irritating for clients wanting to return
movable-but-not-copyable types:
thread<fstream> t = launch_thread(g);
...
fstream f = t(); // ok, move returns
Here, using concepts, thread<fstream> is specialized to not even
allow a return-by-copy. Return-by-move is the only game in town.
future<fstream> will never compile because it demands copyability of
the return type.
> As an example in favor of separating return values and threads,
> consider the case of a thread pool. On startup, it creates (say)
> eight threads. When a task is submitted, a free thread is assigned
> to this task and a future is returned to the caller (see
> pooled_executor in my first post.)
>
> The key here is that you don't know the return type in advance; the
> same thread can be used to execute tasks with different return values.
But thread pools could always use thread<void> (just as they
effectively do in your current design). And we can even convert
thread<R> to thread<void> (tested in my current prototype) to make
such pools even easier to create. Given that we can specialize
thread<void> to assure it has no performance impact of trying to
support a return type that isn't there, I'm seeing only benefits and
no downside to supporting thread<non-void> (maybe besides having to
type "<void>").
-Howard
More information about the cpp-threads
mailing list