Boost logo

Boost :

From: Anthony Williams (anthony_w.geo_at_[hidden])
Date: 2007-07-09 05:10:28


"Ninel Evol" <myLC_at_[hidden]> writes:

> Anthony Williams wrote:
>
>> The current C++0x thread library proposal (http://www.open-
>> std.org/jtc1/sc22/wg21/docs/papers/2007/n2320.html) includes
>> request_cancellation(), which requests a thread cancel at
>> the next call to cancellation_point() or one of the standard
>> cancellation points (such as waiting on a condition
>> variable). I intend to add this facility to Boost.thread
>> once the move to subversion is complete.
>
> Yes - and it's useful. I would like to see both possibili-
> ties! The cancellation by "polling" is most useful when
> writing server- or lib-code. A GUI friendly approach has to
> be different however.
> If you have to poll anyway - why go through all the thread
> trouble anyhow - just poll the GUI event loop instead.
> Creating worker threads is usually done for convenience.
> Polling is inconvenient.
>
> I read the proposal. One thing that stroke me was the list
> of recommended cancellation points. They all somehow imply
> that the thread is already "on hold" (if not polling).
> Let's look at real life examples:
> 1. You have an editor or whatever application; the user has
> selected a file that is to be read in. For convenience,
> the reading takes place in a background thread. The user
> has the possibility to cancel the operation.
> Suppose the file is large and being read over a dead
> slow NFS connection. So (s)he chooses to cancel.
> Where is you cancellation point in there?
> Is the application supposed to get stuck (as many do)?

The platform is free to provide additional cancellation points if
required. However, there is no guaranteed cancellation point in any other
operation, as this would require modifying the OS-supplied functions.

> 2. The classical worker thread pattern:
> You have a really tight loop computing something. You
> just don't want to poll in there as it hurts. The com-
> pare operation for checking out of the loop every once
> in a while already costs too much. What do you do?
> Currently only "dirty killing" is what I could think of
> and that usually means that you have to do the cleanup
> in the main thread (not object-oriented).

If you really feel you have no choice then get the native_handle() for the
thread and terminate your thread using platform APIs. Testing for cancellation
shouldn't be expensive, but I agree that it will cost CPU time.

> Those patterns can be found in many (if not the majority) of
> GUI applications. Sure, you can write around it (see Qt) -
> but that's rather ugly coding...

Yes. Please bear in mind that this is a base-level proposal. TR2 will build on
this proposal, as will later versions of the C++ Standard. Additional
cancellation points may be added, particularly in IO operations.

>> This is very messy --- it's asynchronous, so will break all
>> sorts of stuff. POSIX asynchronous thread cancellation has
>> successfully demonstrated that it's a bad idea, except for
>> very specific cases.
>
> Posix' threads came late - too late. And as usual they
> didn't really go for a "pragmatic approach". The old saying:
> "Posix is better than nothing".

In this case, POSIX is worse than nothing.

> I agree that it's a bit messy behind the scenes. However,
> you can have it the easy way. If you put the thread to sleep
> first - your patient is in a coma; operating on it is not so
> tricky then. That was only a bare example, you can of course
> synchronize it (and you might want to do that in many
> cases;o).

That's not the problem. The problem comes for the thread being cancelled ---
if you cancel it asynchronously, it may be in the middle of any operation,
including those that normally can't fail, such as an unsigned integer
addition. If your code may be cancelled asynchronously, you need to assume
every operation can fail, and that makes correct and robust programming
essentially impossible for any suitably complex task. It can be made to work
for small regions, but not for large regions of code.

Please bear in mind that the proposed cancellation makes the cancelled thread
throw a normal C++ exception from a call to cancellation_point(). Asynchronous
cancellation would cause that exception to be thrown at unpredictable places.

> Again, let's take a look at an example:
> A trickier one - cancellation while retaining data:
> For example the application has computed for hours and you
> want to abort the operation and let it finish tomorrow 'coz
> you need to phone ant Hermine and you only have one line...
> Silly code example:
>
> // either declared in the object or on the heap:
> map< BigUInt, answer_type > *war_dial;
>
> // then in the thread you have to put those
> // critical accesses within a "critical section":
> noAbortBegin();
> war_dial[ number ] = answer;
> noAbortEnd();
>
> // or for the forgetful as an object:
> {
> noAbort na;
> war_dial[ number ] = answer;
> }
>
> Behind the scenes this basically increments an atomic
> variable (check and set). So if you have a cancellation
> request from another thread (say the main thread), the
> worker thread is put to sleep. Then the no-abort variable is
> checked, if not zero a flag is set to indicate to the thread
> to throw an abort-request itself the next time it ends a
> "critical section" and the counter is zero (nesting).
> If the no-abort is unchecked (count of zero) you can fix the
> instruction pointer to let the thread throw an abort-request
> when it wakes up. Then you wake the sleeping thread and
> either wait for it to die, wait with a timeout - or (in case
> it's all on the heap) simply not care anymore. For the
> latter you have to make sure not to leave any children
> running while the parents go dead of course...

If war_dial is not a cancellation point, just put a call to
cancellation_point() before and after the call. If war_dial *is* a
cancellation point, but you don't want it to cancel, use a
std::disable_cancellation object to disable cancellation across the call.

> The possibility to cause an exception within a thread no
> matter what operation it's doing is very powerful.

Too powerful.

> Asking a
> programmer to protect critical accesses on persistent
> data/objects with "critical sections" if using that feature
> is not too much (and incrementing a variable is rather cheap
> as well).

Yes, it is. It is all too easy to forget one "critical section", especially in
C++ where lots of operations are implicit.

> Hope I don't seem to stubborn about this, but I really think
> that once it would be there C++/C-0x programmers wouldn't want
> to miss such a feature anymore.

I am strongly opposed to asynchronous exception injection and asynchronous
cancellation.

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