Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can a mutex lock twice without unlock in C++?

Tags:

c++

int main(){

        std::mutex mut;
        mut.lock();
        cout<<"1111\n";
        mut.lock();      
        cout<<"2222\n";
        return 0;
}

Why does this code work and output 2222? Shouldn't it block at the second lock()? In the mutex source code, the operation lock throws an exception. Shouldn't it block and wait? I use try{...}catch(exception& e){} to catch this exception but it doesn't work.

    void
    lock()
    {
      int __e = __gthread_mutex_lock(&_M_mutex);

      // EINVAL, EAGAIN, EBUSY, EINVAL, EDEADLK(may)
      if (__e)
    __throw_system_error(__e);
    }
like image 677
woder Avatar asked Jan 25 '23 21:01

woder


2 Answers

It cannot. Your code has undefined behavior.

From cppreference:

If lock is called by a thread that already owns the mutex, the behavior is undefined: for example, the program may deadlock. An implementation that can detect the invalid usage is encouraged to throw a std::system_error with error condition resource_deadlock_would_occur instead of deadlocking.

Reading on, one might get the impression that calling lock on the same thread would trigger an exception always:

Exceptions

Throws std::system_error when errors occur, including errors from the underlying operating system that would prevent lock from meeting its specifications. The mutex is not locked in the case of any exception being thrown.

However, there is not necessarily an exception when you call lock in the same thread. Only if the implementation can detect such invalid use and only if the implementation is kind enough to actually throw an exception.

Looking into the standard we find:

1 The class mutex provides a non-recursive mutex with exclusive ownership semantics. If one thread owns a mutex object, attempts by another thread to acquire ownership of that object will fail (for try_­lock()) or block (for lock()) until the owning thread has released ownership with a call to unlock().

2 [Note: After a thread A has called unlock(), releasing a mutex, it is possible for another thread B to lock the same mutex, observe that it is no longer in use, unlock it, and destroy it, before thread A appears to have returned from its unlock call. Implementations are required to handle such scenarios correctly, as long as thread A doesn't access the mutex after the unlock call returns. These cases typically occur when a reference-counted object contains a mutex that is used to protect the reference count. — end note ]

3 The class mutex meets all of the mutex requirements ([thread.mutex.requirements]). It is a standard-layout class ([class.prop]).

4 [Note: A program can deadlock if the thread that owns a mutex object calls lock() on that object. If the implementation can detect the deadlock, a resource_­deadlock_­would_­occur error condition might be observed. — end note ]

5 The behavior of a program is undefined if it destroys a mutex object owned by any thread or a thread terminates while owning a mutex object.

It only says "can deadlock" and "might be observed" but otherwise it does not define what happens when you call lock in the same thread.

PS Requiring to throw an excpetion in this case would make calling lock more expensive without any real benefit. Calling lock twice in the same thread is something that you should just not do. Actually you shouldn't call std::mutex::lock directly at all, because manually releasing the lock is not exception safe:

// shared block - bad
{
    mut.lock();
    // ... code ...
    mut.unlock();
}

If ...code.. throws an exception then mut is never unlocked. The way to solve that is RAII and fortunately the standard library offers lots of RAII-helpers for locks, eg std::lock_guard:

// shared block - good
{
    std::lock_guard<std::mutex> lock(mut);
    // ... code ...
}

TL;DR Don't do it.

If your code relies on that exception then you have more severe problems than not getting that exception.

like image 156
463035818_is_not_a_number Avatar answered Feb 06 '23 09:02

463035818_is_not_a_number


You are working with a single thread. You should read on what a mutex really does and when/how it is used.

As of why it outputs 2222, it might as well output anything else or even make your neighborhood explode, since:

If lock is called by a thread that already owns the mutex, the behavior is undefined: for example, the program may deadlock. 

https://en.cppreference.com/w/cpp/thread/mutex/lock

like image 45
mfnx Avatar answered Feb 06 '23 09:02

mfnx