Search code examples
c++multithreadingc++11mutexc++17

A shared recursive mutex in standard C++


There is a shared_mutex class planned for C++17. And shared_timed_mutex already in C++14. (Who knows why they came in that order, but whatever.) Then there is a recursive_mutex and a recursive_timed_mutex since C++11. What I need is a shared_recursive_mutex. Did I miss something in the standard or do I have to wait another three years for a standardized version of that?

If there is currently no such facility, what would be a simple (first priority) and efficient (2nd priority) implementation of such a feature using standard C++ only?


Solution

  • Recursive property of the mutex operates with the term "owner", which in case of a shared_mutex is not well-defined: several threads may have .lock_shared() called at the same time.

    Assuming "owner" to be a thread which calls .lock() (not .lock_shared()!), an implementation of the recursive shared mutex can be simply derived from shared_mutex:

    class shared_recursive_mutex: public shared_mutex
    {
    public:
        void lock(void) {
            std::thread::id this_id = std::this_thread::get_id();
            if(owner == this_id) {
                // recursive locking
                count++;
            }
            else {
                // normal locking
                shared_mutex::lock();
                owner = this_id;
                count = 1;
            }
        }
        void unlock(void) {
            if(count > 1) {
                // recursive unlocking
                count--;
            }
            else {
                // normal unlocking
                owner = std::thread::id();
                count = 0;
                shared_mutex::unlock();
            }
        }
    
    private:
        std::atomic<std::thread::id> owner;
        int count;
    };
    

    The field .owner needs to be declared as atomic, because in the .lock() method this field is checked without a protection from the concurrent access.

    If you want to recursively call .lock_shared() method, you need to maintain a map of owners, and accesses to that map should be protected with some additional mutex.

    Allowing a thread with active .lock() to call .lock_shared() makes implementation to be more complex.

    Finally, allowing a thread to advance locking from .lock_shared() to .lock() is no-no, as it leads to possible deadlock when two threads attempt to perform that advancing.


    Again, semantic of recursive shared mutex would be very fragile, so it is better to not use it at all.