Search code examples
c++multithreadingmutexatomiccondition-variable

Using std::condition_variable with custom (spin) mutex?


I'm encountering difficulties using std::condition_variable with my custom spin mutex implementation. It seems that std::condition_variable expects a std::mutex to be associated with its lock, leading to compilation errors when attempting to use a different mutex implementation.

Here's a simplified version of my code where the compilation error occurs:

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

class SpinMutex {
public:
    void lock() {
        // Implement spin lock logic here
    }

    void unlock() {
        // Implement spin unlock logic here
    }

private:
    // Implement atomic flag or other spin lock mechanism here
};

SpinMutex mutex;
std::condition_variable cv;
bool ready = false;

void worker_thread() {
    // Do some work...
    std::this_thread::sleep_for(std::chrono::seconds(1));

    // Attempting to lock a SpinMutex with std::lock_guard
    // This line will throw a compilation error
    std::unique_lock<SpinMutex> lock(mutex);

    ready = true;
    cv.notify_one();
}

int main() {
    std::thread worker(worker_thread);

    {
        std::unique_lock<SpinMutex> lock(mutex);
        cv.wait(lock, [] { return ready; });
    }

    worker.join();
    return 0;
}

As shown, the use of std::unique_lock with SpinMutex throws a compilation error. It appears that std::condition_variable is tightly coupled with std::mutex, making it incompatible with custom mutex implementations like a spin mutex.

I think I can use std::condition_variable_any but my question is why the standard decided to go this way? Is it because of legacy purposes or because they implement a special optimization for std::mutex. In other words, for a non-genius developer e.g. SpinMutex above, can I guarantee that std::mutex will outperform my own implementation in general?

VS Implementation capture VS Implemantation


Solution

  • I think I can use std::condition_variable_any

    Yes, correct.

    but my question is why the standard decided to go this way?

    The purpose of the synchronization API's in C++ is to provide a portable wrapper around OS synchronization API's. And the design of the C++ API was highly influenced by the Linux pthreads API.

    So std::mutex and std::condition_variable are meant to be as thin wrappers as possible around the underlying OS API. In terms of pthreads, std::mutex maps to pthread_mutex_t. And std::condition_variable maps to pthread_cond_t.

    And a pthread_cond_t waits on a pthread_mutex_t. But at the time it was realized that:

    1. It is reasonable to want to wait on something besides a std::mutex, and
    2. It will be more expensive to wait on a generalized concept of mutex than on a std::mutex.

    Thus std::condition_variable_any was designed to meet the needs of those wanting to wait on a generalized mutex concept while not making those who don't need that generality pay for the feature.

    Here is an early paper with the design rationale:

    https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2406.html#condition

    What this paper calls cond_var was eventually renamed to condition_variable. And what this paper calls gen_cond_var was eventually renamed to condition_variable_any.

    Also of note: this paper goes into detail about the difficulty of implementing gen_cond_var. However a bug still existed in this paper's implementation. It was more difficult than even the authors realized at the time. However all major implementations of std::condition_variable_any are bug free as of C++11 as far as I know.