Boost logo

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