Jerry M
Jerry M

Reputation: 332

std::condition_variable::notify_all() - I need an example

I need an example of using notify_all() method. Because I cannot understand how should it work.

Every waiting thread begins with code like that:

std::unique_lock<std::mutex> lock(mutex);
condition_variable.wait(lock, [](){return SOMETHING;});

At the very beginning, waiting thread needs to acquire a mutex. So if there are more than one waiting thread, rest of them will wait to lock a mutex. So what is the purpose of using notify_all() if waiting threads stuck at locking mutex and do not execute a method wait() at all? These threads will wake up one by one instead of simultaneously.

Upvotes: 3

Views: 8543

Answers (1)

Richard Hodges
Richard Hodges

Reputation: 69854

The mutex guards the internal state of the condition_variable. Calling wait on the condition_variable causes the mutex to be unlocked. So while waiting, threads do not own the mutex.

When the wait completes, the mutex is again (atomically) acquired before the call to wait returns.

The threads are not contending on the mutex, they are contending on the condition itself.

You are free to unlock the lock as soon as you return from wait if you wish. If you want to allow multiple threads to synchronise on a condition, for example, this is how you would do it. You can also use this feature to implement a semaphore.

example:

This code processes things in batches of 10. Note that notify_all() goes after the unlock():

#include <condition_variable>
#include <mutex>
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <vector>

void emit(std::string const& s)
{
    static std::mutex m;
    auto lock = std::unique_lock<std::mutex>(m);
    std::cout << s << std::endl;
}

std::mutex m;
std::condition_variable cv;
int running_count = 0;

void do_something(int i)
{
    using namespace std::literals;

    auto lock = std::unique_lock<std::mutex>(m);
    // mutex is now locked

    cv.wait(lock,   // until the cv is notified, the mutex is unlocked 
            [] 
            {
                // mutex has been locked here 
                return running_count < 10; 
                // if this returns false, mutex will be unlocked again, but code waits inside wait() for a notify()
            });
    // mutex is locked here
    ++running_count;
    lock.unlock();
    // we are doing work after unlocking the mutex so others can also
    // work when notified
    emit("running " + std::to_string(i));
    std::this_thread::sleep_for(500ms);
    // manipulating the condition, we must lock
    lock.lock();
    --running_count;
    lock.unlock();
    // notify once we have unlocked - this is important to avoid a pessimisation.
    cv.notify_all();
}

int main()
{
    std::vector<std::thread> ts;
    for (int i  = 0 ; i < 200 ; ++i)
    {
        ts.emplace_back([i] { do_something(i); });
    }

    for (auto& t : ts) {
        if (t.joinable()) t.join();
    }

}

Upvotes: 11

Related Questions