|
Boost : |
From: Howard Hinnant (hinnant_at_[hidden])
Date: 2007-03-28 14:49:59
On Mar 28, 2007, at 1:26 PM, Peter Dimov wrote:
> In particular, we want, given a std::thread X, thread A (or threads
> A1...An)
> to be able to join it and thread B to be able to cancel it,
> unblocking A.
>
> This can't be achieved by a simple user-level mutex protection
> because it
> will deadlock. Thread A will lock the mutex protecting X, join and
> block
> with the mutex locked. Thread B will attempt to acquire the mutex in
> order
> to invoke cancel and also block. Unless the target thread exits by
> itself, A
> and B will wait forever.
That's a good use case, thanks.
Let's look at this with a diagram (ascii art warning):
A B
\ /
join cancel
\ /
\/
X
This is actually one of the diagrams I worry about. :-) In your use
case B affecting A's worker thread X is a desired effect. However I
worry that X gets canceled *accidently* out from under A when B
assumes that X is *its* worker thread and doesn't know about A.
In the sole-ownership model, one redesigns the logic so that thread
ownership is a tree instead of a lattice. In this case, a very simple
tree:
B
|
cancel
|
A
|
join
|
X
Now X is a *private* implementation detail of A. And since join is a
cancellation point, when B cancels A, A reacts (unless cancellations
are disabled or the join has already returned). Subsequently
X.~thread() runs and cancels X.
So what if you only want to cancel X and you want A to continue on?
Ok, that could happen. But normally A called X for a reason, and if X
has an abnormal termination, A is going to have to deal with it, or
result in abnormal termination itself.
But if A really wants to deal with this situation there are a few
options:
1. Use shared_future instead of thread.
I.e. this would set up the first (multi-owner) diagram above. And now
when B cancels X, the join doesn't just silently return as if
everything is ok, it throws a child_canceled exception. Now you know
A has to deal with it! :-)
2. Use thread and set up the second (single-owner) diagram:
void A()
{
try
{
X.join();
}
catch (std::thread_canceled&)
{
B canceled me, recover
}
continue
}
3. Build your own multi-owner access to X (this would be my least
recommended choice). But it would involve putting X on the heap along
with a mutex and condition, then building "join" out of the mutex and
condition instead of using thread::join (which is exactly what
shared_future will do too).
-Howard
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk