
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