[cpp-threads] Comments on n2094
Ion Gaztañaga
igaztanaga at gmail.com
Fri Sep 29 23:50:13 BST 2006
Peter Dimov wrote:
> Some brief comments:
>
> Ion Gaztañaga wrote:
>
>> a) The first consequence is that implementing an upgradable_mutex
>> using the suggested two condition variables + a mutex is not correct,
>> since all operations (including unlock()) will need to lock the
>> internal mutex, and that internal mutex locking can throw. Not that
>> this is worries me much, but it's a bit annoying.
>
> This is only a problem if mutex::lock can succeed once and fail the
> second time. This is usually not true for designs where failure comes
> from allocating resources on first lock.
I'm taking some code snippets from the paper "Futexes Are Tricky"
(http://people.redhat.com/drepper/futex.pdf) where glibc/linux mutex
implementation philosophy is sketched. For example, a mutex is
constructed with just an integer member:
class mutex
{
public:
mutex()
: val(0)
{}
void lock()
{
int c;
if((c = compxchg(val, 0, 1)) != 0) {
if(c != 2)
c = xchg(val, 2);
while(c != 0){
futex_wait(&val, 2);
c = xchg(val, 2);
}
}
}
void unlock()
{
if(atomic_dec(val) != 1){
val = 0;
futex_wake(&val, 1);
}
}
private:
int val;
};
The key is that the mutex does not store any resource handle. If there
is no contention, no kernel call is made. When there is contention, a
kernel call is made and that's when the race is solved and resources are
acquired. We might think that we can have an additional atomic flag in
the mutex to acquire resources in the first lock() call and then set the
flag indicating that resources are already there. However, this
disallows using the mutex as a process-shared mutex because once the
file where mutex is constructed is remapped the information is not
correct. On the other hand, the shown mutex can be used as a
process-shared mutex, but resources could be acquired at any moment,
when there is contention and we need to queue the thread in the kernel.
I've just realized that a thread can only be blocked in a single kernel
synchronization object so the kernel can allocate a single queue
resource compatible with all synchronization resources per thread when
the thread is created and use it when the thread needs to block on a
mutex/semaphore/condition. The resource is cached for that thread again
and it's always available so we might avoid any resource acquisition
error, even when lazy initialization is used. The same resource can be
transferred from a condition variable kernel queue to a mutex queue when
the condition is signaled and the thread relocks the external mutex.
This still does not solve the EDEADLK problem, though. But without
EDEADLOCK, maybe a no-throw mutex lock can be guaranteed. Revising
linux, futex system call (http://ds9a.nl/futex-manpages/futex2.html):
"All operations may return -EINVAL in case of unaligned futexes, as well
as -EFAULT, -EPERM, -EACCESS when passing pointers to bad or
inaccessible memory."
So it seems that the system call can't fail due to resource constraints.
We suppose that we always pass aligned integers to the system call when
using mutexes so we can ignore the rest of errors or just abort if that
happens. A no-throw mutex lock simplifies things a lot.
>> 0.1 Condition variables
>> ------------------------
>>
>> Condition variables and exceptions are a very tricky combination,
>> IMHO. In this proposal, the situation is more tricky, since condition
>> variables can work with user-defined mutexes (which is a great
>> feature). If mutex locking can throw, that means that condition wait
>> can also throw, since it must lock the mutex after it's been
>> notified.
>
> condition::wait is a cancelation point and it can fail with EINVAL or
> EPERM, so it doesn't matter whether locking a mutex can throw;
> condition::wait can throw.
>
>> What is the user supposed to do if a condition throws? Can the user
>> recover resources and try it again? Can we guarantee that the mutex
>> will be always relocked?
>
> POSIX says yes. The mutex is in a locked state when pthread_cond_wait
> "throws" a cancelation exception or returns an error.
Agreed. The key is that N2094 outlines a condition that is compatible
with user-defined mutexes, so we need to state clearly a protocol to
guarantee a no-throw mutex relock, or just require a no-throw lock()
function.
Regards,
Ion
More information about the cpp-threads
mailing list