0

I've read from https://en.cppreference.com/w/cpp/thread/condition_variable/wait that wait() "Atomically unlocks lock". How do I see this via std::cout? I am trying to understand conditional variables better on what they're actually doing. I've wrote an attempt below.

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

using namespace std;

condition_variable cv;
mutex m;
bool stopped = false;

void f1() {
    unique_lock<mutex> ul{m};
    cout << "f1: " << ul.owns_lock() << endl;
    cv.wait(ul, [&]{
        cout << "f1: " << ul.owns_lock() << endl;
        return stopped;
    });
    cout << "f1 RUNNING\n";
    cout << "f1: " << ul.owns_lock() << endl;
}


void f2() {
    lock_guard<mutex> lg{m};
    cout << "f2 RUNNING\n";
}

int main() {
    unique_lock<mutex> ul{m};
    thread t1(&f1);
    thread t2(&f2);

    cout << ul.owns_lock() << endl;
    this_thread::sleep_for(chrono::seconds(1));
    stopped = true;
    cv.notify_one();
    cout << ul.owns_lock() << endl;
    ul.unlock();
    cout << ul.owns_lock() << endl;
    this_thread::sleep_for(chrono::seconds(1));

    t1.join();
    t2.join();
    return 0;
}
davidj361
  • 147
  • 7
  • 1
    You cannot ever see owns_lock() returning false, because the thread immediately goes to sleep after wait() has unlocked the mutex. You can notify() the thread and it will then execute the predicate function to determine whether to continue waiting, but during this check the mutex will be reaquired and owns_lock() will return true. – Sven Nilsson Oct 07 '22 at 17:07
  • Does this answer your question? [How is conditional\_wait() implemented at the kernel and hardware/assembly level?](https://stackoverflow.com/questions/33431953/how-is-conditional-wait-implemented-at-the-kernel-and-hardware-assembly-level) – OznOg Oct 07 '22 at 18:20

1 Answers1

3

std::unique_lock and std::lock_guard work with any class type that satisfies the requirements of BasicLockable. So, just write your own class that wraps a std::mutex, then you can add whatever logging you want.

UPDATE: However, std:condition_variable only works with std::mutex specifically, so if you write your own mutex wrapper class then you will have to use std::condition_variable_any instead.

For example:

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

using namespace std;

struct LoggingMutex
{
    mutex m;

    void lock() {
        cout << "Locking" << endl;
        m.lock();
        cout << "Locked" << endl;
    }

    bool try_lock() {
        cout << "Attempting to lock" << endl;
        bool result = m.try_lock();
        cout << (result ? "Locked" : "Not locked") << endl;
        return result;
    }

    void unlock() {
        cout << "Unlocking" << endl;
        m.unlock()
        cout << "Unlocked" << endl;
    }
};

condition_variable_any cv;
LoggingMutex lm;
bool stopped = false;

void f1() {
    unique_lock<LoggingMutex> ul{lm};
    cout << "f1: " << ul.owns_lock() << endl;
    cv.wait(ul, [&]{
        cout << "f1: " << ul.owns_lock() << endl;
        return stopped;
    });
    cout << "f1 RUNNING\n";
    cout << "f1: " << ul.owns_lock() << endl;
}


void f2() {
    lock_guard<LoggingMutex> lg{lm};
    cout << "f2 RUNNING\n";
}

int main() {
    unique_lock<LoggingMutex> ul{lm};
    thread t1(&f1);
    thread t2(&f2);

    cout << ul.owns_lock() << endl;
    this_thread::sleep_for(chrono::seconds(1));
    stopped = true;
    cv.notify_one();
    cout << ul.owns_lock() << endl;
    ul.unlock();
    cout << ul.owns_lock() << endl;
    this_thread::sleep_for(chrono::seconds(1));

    t1.join();
    t2.join();
    return 0;
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 1
    `std::condition_variable` takes `std::unique_lock` specifically; it won't work with `unique_lock`. Your example in fact [doesn't compile](https://godbolt.org/z/bPeaq6KfG). You might have meant to use `std::condition_variable_any` – Igor Tandetnik Oct 08 '22 at 21:50