-1

Youtube details

I have been browsing youtube to try and develop my understanding of C++ multithread support with mutex and condition variables.

I came across this video. Skip to time 6:30 to see what I am currently looking at. (A page of code.)

I believe there is a mistake in the code, but I wanted to check. It could just as well be that I don't understand something.

Question

The author states that std::unique_lock locks the mutex on creation. Meaning that there is no need to call

unique_lock<mutex> lock(m)
lock.lock(); // this is wrong, because unique_lock already locked the mutex

after creating a unique_lock object.

I assume although I do not know for certain, that unique_lock will release the mutex lock on destruction. (Aka when it goes out of scope.)


Can it also be unlocked manually by calling

lock.unlock()

? From the documentation it appears there is no such unlock function. It looks like unique_lock is therefore the same as scoped_lock? But again, I'm assuming this isn't the case and there's some other information I am missing.


Continuing... The author has a function which looks like this:

void addMoney(int money)
{
    std::lock_guard<mutex> lg(m); // lock_guard being used interchangably with unique_lock - why?
    balance += money; // adding to global variable

    cv.notify_one(); // error here
                     // the lock_guard is still in scope
                     // the mutex is still locked
                     // calling notify_one() may cause the sleeping thread to wake up
                     // check if the mutex is still locked (which it might be if the
                     // destructor for lg hasn't finished running)
                     // and then go back to sleep
                     // meaning this line of code may have no effect
                     // it is undefined behaviour
}

I have anotated where I believe there is an error. I think this function causes undefined behaviour, because the lock_guard is still in scope, and therefore the mutex might be locked.

Effectively it is a race condition:

  • If addMoney() ends before the other function begins, we are ok
  • If the other function withdrawMoney() checks the lock (cv.wait()) before addMoney() exits then the program breaks, and remains in a locked state

For completeness here is the other function:

void withdrawMoney(int money)
{
    std::unique_lock<mutex> ul(m); // unique_lock instead of scoped_lock? why?
    cv.wait(ul, []{return balance != 0;});
    // some more stuff omitted
}

Summary

There are a couple of points I have raised

  • Most importantly the race condition
  • Of secondary importance, why are two different things (lock_guard and unique_lock) being used to do what appears to be the same thing (performing the same function)
FreelanceConsultant
  • 13,167
  • 27
  • 115
  • 225
  • Uhm, did you misread [the docs](https://en.cppreference.com/w/cpp/thread/unique_lock/unlock) – JHBonarius Mar 19 '21 at 12:44
  • FWIW, C++ is hard to get right. Instead of using a youtube video or online tutorials, I suggest getting yourself a [good C++ book](http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list). They can get into much more detail. – NathanOliver Mar 19 '21 at 12:44
  • 2
    Several distinct questions here. Had you only asked about the "race", [this would be the duplicate](https://stackoverflow.com/questions/17101922/do-i-have-to-acquire-lock-before-calling-condition-variable-notify-one). – Drew Dormann Mar 19 '21 at 12:44
  • 3
    You don't see `unlock()` in the documentation link you cited? And, no, it is not a race condition, and is actually the correct and proper way to use a condition variable. Unfortunately, random Youtube videos are not an effective way to learn C++. Any clown can upload a video to Youtube and ramble on any subject. The only effecitve way to learn C++ [is with a qualitied, edited, textbook](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list), which will fully explain the core fundamentals of the most complicated, and hardest to learn, programming language in use today. – Sam Varshavchik Mar 19 '21 at 12:45
  • 1
    When the waiting thread is notified, if it sees the mutex is locked, it may go back to sleep. But now it is waiting for the mutex, not for a condition notification. So when the mutex becomes unlocked, it will eventually wake and resume. It is not an error to `notify` while holding the associated `mutex` lock. – François Andrieux Mar 19 '21 at 13:03
  • 1
    `std::unique_lock` is an implementation of the [Ressource Acquisition Is Initialization](https://en.cppreference.com/w/cpp/language/raii) pattern where a mutex lock is the resource. Reading about and understanding that pattern will probably answer some of your questions. – François Andrieux Mar 19 '21 at 13:07
  • @FrançoisAndrieux Ok that makes thanks – FreelanceConsultant Mar 19 '21 at 13:11
  • @SamVarshavchik Ok I do see it now! – FreelanceConsultant Mar 19 '21 at 13:11

2 Answers2

1

That comment

// calling notify_one() may cause the sleeping thread to wake up
// check if the mutex is still locked (which it might be if the
// destructor for lg hasn't finished running)
// and then go back to sleep

is incorrect. There are two separate control mechanisms here: the condition variable and the mutex. Waking up on a notification to a condition variable means, simply, waking up. After waking up, the thread blocks waiting for the mutex. When the mutex is released by the thread that called notify_one(), the blocked thread (or perhaps some other thread, but eventually, the blocked thread) gets the mutex and continues execution. It does not go back to waiting for the condition variable.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
0

For some more explanation on std::unique_lock vs std::lock_guard, see this question.

There is no undefined behavior when sending the notification while the mutex is still locked. It might cause an unnecessary thread-switch especially if the receiving thread has a higher priority, but this is just a small performance hit. It is also not needed to have the mutex locked to send the notification, so the function may be written as:

void addMoney(int money)
{
  {
    std::lock_guard<mutex> lg(m);
    balance += money;
  }
  cv.notify_one();
}

You have to make sure that the resources for the condition are protected when changing and when checked.

stefaanv
  • 14,072
  • 2
  • 31
  • 53