[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