Is my waiting mechanism - notifications using std :: mutex correct?

I started using std :: mutexes to stop a thread and wait for another thread to resume it. It works like this:

Topic 1

// Ensures the mutex will be locked
while(myWaitMutex.try_lock());
// Locks it again to pause this thread
myWaitMutex.lock();

      

Topic 2

// Executed when thread 1 should resume processing:
myWaitMutex.unlock();

      

However, I'm not sure if this is correct and will work without problems on all platforms. If this is not true, what is the correct way to implement this in C ++ 11?

+3


source to share


4 answers


Code problems

// Ensures the mutex will be locked
while(myWaitMutex.try_lock());

      

.try_lock()

tries to acquire the lock and returns true

if successful, meaning the code says, "If we use the lock, try again to lock it over and over until we hit." We can never "fail" because we currently have the castle we are waiting for, and therefore it will be an endless loop. Also, a blocking attempt with a help std::mutex

that the caller has already purchased is UB, so it is guaranteed to be UB. If failed, it .try_lock()

will return false

and the cycle while

will be completed. In other words, it will not guarantee that the mutex will be locked.

The correct way to lock a mutex is simply:

myWaitMutex.lock();

      

This will block the current thread (indefinitely) until it can acquire the lock.

Next, another thread tries to unlock a mutex that does not have a lock.

// Executed when thread 1 should resume processing:
myWaitMutex.unlock();

      

This won't work like UB before .unlock()

on std::mutex

, in which you don't have a lock yet.

Using locks



When using mutex locks, it is easier to use a RAII wrapper object for example std::lock_guard

. The usage pattern is std::mutex

always: "Block β†’ do something in the critical section β†’ unblock". A will std::lock_guard

lock the mutex in its constructor and unlock it in its destructor. No need to worry about when to lock and unlock and low level things like that.

std::mutex m;
{
    std::lock_guard<std::mutex> lk{m};
    /* We have the lock until we exit scope. */
} // Here 'lk' is destroyed and will release lock.

      

Simple blocking may not be the best tool for the job

If you want to be able to signal a thread to wake up, then wait and notify the structure using . allows any caller to send signal to pending threads without any blocking. std::condition_variable

std::condition_variable

#include <atomic>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>

using namespace std::literals;

int main() {
    std::mutex m;
    std::condition_variable cond;

    std::thread t{[&] {
        std::cout << "Entering sleep..." << std::endl;
        std::unique_lock<std::mutex> lk{m};
        cond.wait(lk); // Will block until 'cond' is notified.
        std::cout << "Thread is awake!" << std::endl;
    }};

    std::this_thread::sleep_for(3s);

    cond.notify_all(); // Notify all waiting threads.

    t.join(); // Remember to join thread before exit.
}

      

However, to further complicate matters, this thing is called false awakenings , which means that any waiting threads can wake up at any time for unknown reasons. This is a fact in most systems and has to do with the inner workings of thread scheduling. Also, we probably need to check that the wait is really necessary since we are dealing with concurrency. If, for example, a notification thread is notified before we start waiting, we can wait forever if we don't have a way to test it first.

To deal with this, we need to add a while loop and a predicate that tells when we need to wait and when we are done, we wait.

int main() {
    std::mutex m;
    std::condition_variable cond;
    bool done = false; // Flag for indicating when done waiting.

    std::thread t{[&] {
        std::cout << "Entering sleep..." << std::endl;
        std::unique_lock<std::mutex> lk{m};
        while (!done) { // Wait inside loop to handle spurious wakeups etc.
            cond.wait(lk);
        }
        std::cout << "Thread is awake!" << std::endl;
    }};

    std::this_thread::sleep_for(3s);

    { // Aquire lock to avoid data race on 'done'.
        std::lock_guard<std::mutex> lk{m};
        done = true; // Set 'done' to true before notifying.
    }
    cond.notify_all();

    t.join();
}

      

There are additional reasons why it is recommended to wait inside a loop and use a predicate such as "stolen awakenings" as pointed out in the comments from @David Schwartz .

+2


source


It seems to me that you are looking for a condition variable . In the end, there should always be a way to make it work through mutexes, but a condition variable is the current idiomatic C ++ way of handling a "block and wait for something to happen" script.



+4


source


The behavior of the mutex when the thread that is holding it tries to block it is undefined. The behavior of a mutex when a thread that is not holding it tries to unlock it is undefined. This way, your code can do anything at all on different platforms.

Use a mutex in conjunction with a condition variable and a boolean predicate instead. In pseudocode:

To block:

  • Get mutexes.

  • While the predicate is false, lock the condition variable.

  • If you want to translate here, set the predicate to false.

  • Release the mutexes.

Release:

  • Get mutexes.

  • Set the predicate to true.

  • Condition variable signal.

  • Release the mutexes.

To flip:

  • Get mutexes.

  • Set the predicate to false.

  • Release the mutexes.

+2


source


Please check this code ....

std::mutex m_mutex;             
std::condition_variable m_cond_var;    

void threadOne(){
    std::unique_lock<std::mutex> lck(mtx);
    while (!ready){ 
        m_cond_var.wait(lck);
    }
    m_cond_var.notify_all();
}
void threadTwo(){
    std::unique_lock<std::mutex> lck(mtx);
    read = true;
    m_cond_var.notify_all();
}

      

Hope you get a solution. And this is very good!

+1


source







All Articles