Boost logo

Boost :

From: Anthony Williams (anthony_w.geo_at_[hidden])
Date: 2007-03-27 04:21:21


Wow. I leave things for a few days and there's loads of messages to deal with.

Re cancellation:

Everyone I've ever spoken to about C++ having standard thread support has said
"excellent, that'll mean we'll have proper cancellation support" or
similar. IMO, we can't afford to not have cancellation.

Cancellation must operate as-if it's an exception, so stacks get unwound
correctly. It must also get caught by catch(...) so catch(...) { cleanup();
throw;} works correctly. In which case, it must be stoppable --- I don't want
to introduce an exception that automatically gets rethrown, as that would be a
dangerous precedent. All of which basically means it must be a real C++
exception, so why not give it a name (std::thread_canceled)?

If this means we can't interoperate with pthread_cancel, so be it. I'd rather
pthread_cancel threw std::thread_canceled, though, as that makes it easier to
deal with mixed C/C++ code, and allows for more cancellation points.

Re single/shared ownership and const/non-const operations

I think that cancel/join/detach are non-const operations, as they conceptually
modify the state of the thread. I also think this is orthogonal to
single/shared ownership.

I happen to currently favour single ownership, but that is also separate from
shared access --- just because multiple threads or functions can access the
std::thread object doesn't mean they "own" it. Single ownership does mean that
you can pass a const ref when you want someone to be able to observe the
thread but not modify it.

I think that all operations on std::thread should be safe to execute
concurrently, and that they should have defined functionality in all cases,
which should probably be a no-op.

Thread A Thread B Comment
t.cancel() t.cancel() harmless race: thread may be cancelled twice, if
                            first cancel is swallowed before second cancel
                            raised. If cancel flag already raised, second is
                            no-op.

t.join() t.cancel() harmless race: if join runs first, cancel is no-op

t.join() t.join() harmless race: Both joins will wait until the
                            thread is done. If one join comes first, the
                            thread is already done when the second tries to
                            join, so no-op.

t.detach() t.join() harmless race. Either thread is detached first, so
                            join is no-op, or thread is joined first, so
                            detach is no-op.

t.detach() t.detach() harmless race. Thread is detached. second detach
                            is no-op.

t.detach() t.cancel() harmless race: if detach runs first, cancel is
                            no-op

In my code, this is done by ref counting the thread state. The thread itself
has one reference, and the std::thread object has another. Each modifying
operation takes a local ref to the shared state, modifies it, and then
releases the ref. In the case of join and detach it also clears the ref in the
std::thread object, but that's OK too, as the other threads that might do the
modifying have their own refs, and the internal state is not pulled out from
under them.

Anthony

-- 
Anthony Williams
Just Software Solutions Ltd - http://www.justsoftwaresolutions.co.uk
Registered in England, Company Number 5478976.
Registered Office: 15 Carrallack Mews, St Just, Cornwall, TR19 7UL

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk