Boost logo

Boost :

From: Preston A. Elder (prez_at_[hidden])
Date: 2007-11-06 08:22:00


On Tue, 06 Nov 2007 10:21:26 +0000, Anthony Williams wrote:

> "Preston A. Elder" <prez_at_[hidden]> writes:
> The interface is the same as the "cancellation" interface in N2320
> (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2320.html),
> except renamed to "interruption".
>
> The interruption points are:
>
> thread::join, thread::timed_join
> this_thread::sleep
> condition_variable::wait, condition_variable::timed_wait
> condition_variable_any::wait, condition_variable_any::timed_wait
> this_thread::interruption_point
I appreciate that, however that does not mean you can't go a step
further :)

>> - but unfortunately it does this by doing a broadcast to that condition
>> which means two things.
>
> Firstly, the broadcast only happens on POSIX platforms --- on Windows,
> the thread is notified directly.
My point was, on any single platform, it broadcasts - which in and of
itself is not a bad thing, my interruptable_mutex used a broadcast too,
however the lock the condition was waiting on was not the lock the end-
user would acquire (it was a private lock).

> Condition variables are allowed spurious wake-ups, so the user of the
> condition variable has to allow for this possibility. Yes, it is less
> than ideal that all the other threads are woken too, for the reasons you
> outline. At the moment I'm not sure of a better scheme.

See below.

>
>> Third, interruption should also apply to locks - which would in part
>> solve the above situation. The problem with this of course is that it
>> is less efficient to use a mutex/condition crafted to work with
>> interruptions at the lock level.
>
> Yes, and that's why it doesn't happen. All the interruption points are
> where the thread is waiting for some event (e.g. a condition to be
> notified, a thread to finish, or a time to elapse). Locks should only be
> held for a short period, so blocking for a lock should not be a long
> wait. I don't agree that it should be an interruption point.

I agree, but lets face it, sometimes locks are, by necessity NOT held for
short periods. For example when writing to an I/O device that you want
to ensure that there is only one entity writing to it at a time. In this
case, the writer may hold that lock for significant amounts of time (lets
avoid the design issue of moving the I/O to its own thread, lets for
arguments sake say this is not possible for some reason).

In this case, of course, you could create a condition and get woken up
when you are allowed to write, but why not just lock the mutex and wait
until you get the lock? Thats all you really want to do. Besides - 1)
to even enter the condition you have to acquire the lock so you can
release it, and 2) as soon as the condition is signalled, you will wait
on that lock anyway.

> If a thread is waiting on a condition variable, a signal is not
> guaranteed to abort the wait:
>
> "If a signal is delivered to a thread waiting for a condition variable,
> upon return from the signal handler the thread resumes waiting for the
> condition variable as if it was not interrupted, or it shall return zero
> due to spurious wakeup."
>
> The only way I know to ensure that the thread being interrupted wakes
> from the condition variable is to broadcast the condition variable. The
> alternative is that boost::condition_variable does not actually use a
> pthread_cond_t in the implementation, and I'm not keen on that idea at
> all.
But if you do exactly what you do inside your check - ie. throw an
exception from within the signal, that should break you out of any wait.

>> 2) Move interruption to its own series of structures. Like I did, with
>> an interruptable_mutex. The advantage of this is twofold:
>> 1) It can be implemented in a non-platform specific manner, using
>> existing data structures, so you implement the interruptable version
>> once (utilizing boost::mutex and boost::condition) and you don't have
>> to care about the underlying platform. This means it doesn't require
>> any OS specific support.
>> 2) It works exactly as the user thinks. An interruptable_mutex can
>> be
>> interrupted while blocked on something to do with it (meaning when
>> trying to acquire a lock on it, OR when waiting on a condition with
>> it).
>
> I'm not keen on this approach, as it really does mean that the thread
> can only be interrupted at the defined points.

Fine, then keep the interrupt in the thread - in fact, it is NICE to be
able to interrupt a sleep() and so forth. However, change the way mutex
works. Change mutex::lock() to something like:

IF (!interruption_enabled)
    do hard-lock;
ELSE
    interruption_checker check(internal_cond);
    res = lock.trylock();
    WHILE (!res)
    {
        internal_cond.wait(internal_mutex);
        IF (check.interrupted)
            throw thread_interrupted;
        res = lock.trylock();
    }

Then unlock() becomes:
    lock.unlock();
    internal_cond.notify_one();

This is more or less the crux of my interruptable_mutex anyway. The key
is that while it still uses a broadcast to interrupt, when the condition
wakes up, it will acquire the internal lock (ie. a lock on the mutex's
own meta-data, NOT a the lock the user wants to acquire). This gives you
an interruption point, but also means that if you weren't the thread to
be interrupted, you can go back to waiting for the mutex to be available.

Passing these to conditions is also pretty easy, for example,
condition::wait() becomes something like:

IF (!interruption_enabled)
    cond.wait(lock);
ELSE
    interruption_checker check(cond);
    lock.unlock();
    lock.internal_lock.lock();
    cond.wait(lock.internal_lock);
    lock.internal_lock.unlock();
    if (check.interrupted)
        throw thread_interrupted;
    lock.lock(); // uses the above mutex::lock(), so interruptable.

This way the condition still has the same interruption semantics as you
currently have, however if interruption is enabled, then you do your
condition wait on the thread's internal ('meta-data') mutex, not on the
'main' mutex. Either way your condition ends by acquiring the mutex the
user requested - and ends with something that will synchronize multiple
threads and act as a mutex should. But in addition, you will have
created an interruption point on mutex::lock(). This gives you a LOT of
power and convenience.

I still say that interruption semantics are incomplete without
interruption of a mutex waiting to block. The above can be implemented
in a platform non-specific manner without much trouble and just use the
platform-specific version of mutex.

Preston.

If you can dream it, it can be done :)


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