[cpp-threads] Re: Thread API interface tweaks

Peter Dimov pdimov at mmltd.net
Thu Aug 31 00:13:00 BST 2006


Howard Hinnant wrote:
> 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.

I'm not seeing any problems...

> Consider:
>
> handle h;
>
> ... lots of code ...
>
> cancel(h);
> // is h in the cancel state here?

Yes, it is, unless it has already ended. A thread can't be "uncanceled" from 
the outside.

> ...
> // Has another thread changed the state of h out from under us yet?

Why does it matter? Operations on h obey basic thread safety, i.e. they are 
sequentially consistent.

> 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

In practice, having a noncopyable 'thread' class simply forces people to use 
shared_ptr<thread> as the thread handle, with the added inconvenience that 
now thread::current() is difficult to emulate. So the same caveats still 
apply.

Actually, I understated a bit. People tend to use thread*. When you need to 
refer to a thread from two different contexts, you just have to do that 
somehow. A smart handle just makes it easier and less error prone.

> 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.

thread::handle is not an owner. It is a handle to the thread, something that 
sits logically between your thread and your thread_id. It identifies a 
thread, but doesn't own it; the thread owns itself. You can always retrieve 
a handle to a thread by using thread::current() (QoI: even when the current 
thread is a "foreign" thread). If the unique thread owner is exposed as a 
C++ object, you wouldn't be able to do that.

We can call it thread::id, but I prefer to reserve the id for an atomic POD 
type. :-)

Can't do diagrams in plain text, but the two designs are:

Howard:

1. thread (unique, noncopyable, movable)
// 1.5. thread*, shared_ptr<thread>
2. thread_id (first class value type)

Peter:

1. (unique noncopyable state hidden inside the threading lib)
1.5. thread::handle (refers to 1, first class value)
2.5. thread::id (atomic POD, typically integral or pointer)

>> 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.

Yes, agreed... Returning vector<int> from a thread isn't really common 
today, but who knows.

How about just changing the return type of future<R>::operator() to R const 
& ? You can inspect the R inside the future without problems as long as you 
don't try to modify it. I know that it's not the same, but it might be close 
enough for practical purposes.




More information about the cpp-threads mailing list