[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