4
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);
    }
Boann
  • 48,794
  • 16
  • 117
  • 146
woder
  • 647
  • 5
  • 12
  • 1
    Typing your question into Google turns up a lot of good hits for me. That could be a function of Google knowing what sort of stuff I look for, but worth trying all the same. This question has particularly direct answers about why this is allowed: https://stackoverflow.com/questions/11173532/why-is-locking-a-stdmutex-twice-undefined-behaviour – user4581301 Jul 22 '20 at 16:03
  • 1
    The philosophical _"why"_ of your question is twofold: C++ follows the zero-overhead principle, and C++ is not a nanny language. The language provides you enough rope to shoot yourself in the foot. It expects that your code is a valid program, and your code does not do undefined behavior. When you do undefined behavior, such as in your code, all sorts of strange and bizarre behavior can result... including the most insidious of all: appearing to work correctly. – Eljay Jul 22 '20 at 16:11
  • Interestingly, I went to an interview where a recruiter told me they asked about Mutex's, so I studied them. Apparently, I answered the question well (certainly not perfectly), and indeed got the job. When I actually did the job, it didn't really require knowing the details of mutex's. – JosephDoggie Jul 22 '20 at 16:28

2 Answers2

7

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.

user4581301
  • 33,082
  • 7
  • 33
  • 54
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
1

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

mfnx
  • 2,894
  • 1
  • 12
  • 28