Click here to Skip to main content
13,627,517 members
Click here to Skip to main content
Add your own
alternative version

Stats

23.4K views
25 bookmarked
Posted 12 Dec 2015
Licenced CPOL

RWMutex: A Shared/Exclusive Recursive Mutex

, 10 Dec 2017
Rate this:
Please Sign up or sign in to vote.
A mutex with shared/exclusive access

Introduction

My aim was to create something that could act as a read/write locking mechanism. Any thread can lock it for reading, but only one thread can lock it for writing. Until the writing thread releases it, all other threads wait. A writing thread does not acquire the mutex until any other thread has released.

I could use Slim Reader/Writer locks, but:

  • They are not recursive, e.g., a call to AcquireSRWLockExclusive() will block if the same thread has called the same function earlier.
  • They are not upgradable, e.g., a thread which has locked the lock for read access can't lock it for write.
  • They are not copiable handles.

I could try C++ 14 shared_lock but I still need C++ 11 support. Besides, I'm not yet sure if it can actually fulfill my requirements.

Therefore, I had to implement it manually. The plain C++ 11 way was removed due to lack of WaitForMultipleObjects (nyi).

RWMUTEX

My class is rather simple.

class RWMUTEX
    {
    private:
        HANDLE hChangeMap;
        std::map<DWORD, HANDLE> Threads;
        RWMUTEX(const RWMUTEX&) = delete;
        RWMUTEX(RWMUTEX&&) = delete;

I need a std::map<DWORD,HANDLE> to store handles for all threads that try to access the shared resource, and I also need a mutex handle to make sure that all changes to this map are thread safe.

Constructor
RWMUTEX(const RWMUTEX&) = delete;
void operator =(const RWMUTEX&) = delete;

RWMUTEX()
    {
    hChangeMapWrite = CreateMutex(0,0,0);
    }

Simply create a handle to the changing map mutex. The object should not be copiable.

CreateIf
HANDLE CreateIf(bool KeepReaderLocked = false)
    {
                WaitForSingleObject(hChangeMap, INFINITE);
                DWORD id = GetCurrentThreadId();
                if (Threads[id] == 0)
                    {
                    HANDLE e0 = CreateMutex(0, 0, 0);
                    Threads[id] = e0;
                    }
                HANDLE e = Threads[id];
                if (!KeepReaderLocked)
                      ReleaseMutex(hChangeMap);
                return e; 
    }

This private member function is called when you call LockRead() or LockWrite() to lock the object. If the current thread has not already registered itself to the threads that might access this mutex, this function creates a mutex for that thread. If some other thread has locked this mutex for write access, this function will block until the writing thread releases the object. This function returns the mutex handle for the current thread.

LockRead/ReleaseRead
HANDLE LockRead()
    {
    auto f = CreateIf();
    WaitForSingleObject(f,INFINITE);
    return f;
    }
void ReleaseRead(HANDLE f)
    {
    ReleaseMutex(f);
    }

These functions are called when you want to lock the object for read access and later release it.

LockWrite/ReleaseWrite
void LockWrite()
    {
                CreateIf(true);

                // Wait for all 
                vector<HANDLE> AllThreads;
                AllThreads.reserve(Threads.size());
                for (auto& a : Threads)
                    {
                    AllThreads.push_back(a.second);
                    }

                WaitForMultipleObjects((DWORD)AllThreads.size(), AllThreads.data(), TRUE, INFINITE);

                // Reader is locked
    }

void ReleaseWrite()
    {
    
    // Release All
    for (auto& a : Threads)
        ReleaseMutex(a.second);
    ReleaseMutex(hChangeMap);
    }

These functions are called when you want to lock the object for write access and later release it. LockWrite() function makes sure that:

  1. no new threads are registered during the lock, and
  2. any reading thread has released the lock.
Destructor
~RWMUTEX()
    {
    CloseHandle(hChangeMap);
    hChangeMap = 0;
    for (auto& a : Threads)
        CloseHandle(a.second);
    Threads.clear();
    }

The destructor makes sure that all handles are cleared.

So the entire code is (with some debugging aid):

// RWMUTEX
    class RWMUTEX
        {
        private:
            HANDLE hChangeMap = 0;
            std::map<DWORD, HANDLE> Threads;
            DWORD wi = INFINITE;
            RWMUTEX(const RWMUTEX&) = delete;
            RWMUTEX(RWMUTEX&&) = delete;
            operator=(const RWMUTEX&) = delete;




        public:

            RWMUTEX(bool D = false)
                {
                if (D)
                    wi = 10000;
                else
                    wi = INFINITE;
                hChangeMap = CreateMutex(0, 0, 0);
                }

            ~RWMUTEX()
                {
                CloseHandle(hChangeMap);
                hChangeMap = 0;
                for (auto& a : Threads)
                    CloseHandle(a.second);
                Threads.clear();
                }

            HANDLE CreateIf(bool KeepReaderLocked = false)
                {
                auto tim = WaitForSingleObject(hChangeMap, INFINITE);
                if (tim == WAIT_TIMEOUT && wi != INFINITE)
                    OutputDebugString(L"LockRead debug timeout!");
                DWORD id = GetCurrentThreadId();
                if (Threads[id] == 0)
                    {
                    HANDLE e0 = CreateMutex(0, 0, 0);
                    Threads[id] = e0;
                    }
                HANDLE e = Threads[id];
                if (!KeepReaderLocked)    
                    ReleaseMutex(hChangeMap);
                return e;
                }

            HANDLE LockRead()
                {
                auto z = CreateIf();
                auto tim = WaitForSingleObject(z, wi);
                if (tim == WAIT_TIMEOUT && wi != INFINITE)
                    OutputDebugString(L"LockRead debug timeout!");
                return z;
                }

            void LockWrite()
                {
                CreateIf(true);

                // Wait for all 
                vector<HANDLE> AllThreads;
                AllThreads.reserve(Threads.size());
                for (auto& a : Threads)
                    {
                    AllThreads.push_back(a.second);
                    }

                auto tim  = WaitForMultipleObjects((DWORD)AllThreads.size(), AllThreads.data(), TRUE, wi);
                if (tim == WAIT_TIMEOUT && wi != INFINITE)
                    OutputDebugString(L"LockWrite debug timeout!");

                // We don't want to keep threads, the hChangeMap is enough
                for (auto& a : Threads)
                    ReleaseMutex(a.second);

                // Reader is locked
                }

            void ReleaseWrite()
                {
                ReleaseMutex(hChangeMap);
                }

            void ReleaseRead(HANDLE f)
                {
                ReleaseMutex(f);
                }
        };

To use the RWMUTEX, you can simply create locking classes:

class RWMUTEXLOCKREAD
    {
    private:
        RWMUTEX* mm = 0;
    public:

        RWMUTEXLOCKREAD(const RWMUTEXLOCKREAD&) = delete;
        void operator =(const RWMUTEXLOCKREAD&) = delete;

        RWMUTEXLOCKREAD(RWMUTEX*m)
            {
            if (m)
                {
                mm = m;
                mm->LockRead();
                }
            }
        ~RWMUTEXLOCKREAD()
            {
            if (mm)
                {
                mm->ReleaseRead();
                mm = 0;
                }
            }
    };

class RWMUTEXLOCKWRITE
    {
    private:
        RWMUTEX* mm = 0;
    public:
        RWMUTEXLOCKWRITE(RWMUTEX*m)
            {
            if (m)
                {
                mm = m;
                mm->LockWrite();
                }
            }
        ~RWMUTEXLOCKWRITE()
            {
            if (mm)
                {
                mm->ReleaseWrite();
                mm = 0;
                }
            }
    };

Sample usage:

RWMUTEX m;

// ... other code
void foo1() {
  RWMUTEXLOCKREAD lock(&m);
  }

void foo2() {
 RWMUTEXLOCKWRITE lock(&m);
}

History

  • 10-12-2017: Added debugging aids and fixed a read/write deadlock by allowing ReleaseRead() not to call CreateIf().
  • 12-05-2017: Fixed rare deadlock in writers, and simplified class.
  • 23-08-2016: Fixed deadlock in reader unlocking
  • 17-12-2015: Fixed race bug in reading (and removed C++ 11 implementation)
  • 12-12-2015: First release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Michael Chourdakis
Engineer
Greece Greece
I'm working in C++, PHP , Java, Windows, iOS and Android.

I 've a PhD in Digital Signal Processing and I specialize in Pro Audio applications.

My home page: http://www.michaelchourdakis.com

You may also be interested in...

Pro

Comments and Discussions

 
QuestionA Question about the Mutex Pin
Rick York4-Apr-17 5:17
memberRick York4-Apr-17 5:17 
AnswerRe: A Question about the Mutex Pin
Rick York4-Apr-17 11:41
memberRick York4-Apr-17 11:41 
GeneralRe: A Question about the Mutex Pin
Michael Chourdakis5-Apr-17 4:44
memberMichael Chourdakis5-Apr-17 4:44 
QuestionPerformance degradation - you never delete locks from map Pin
Evgeny Zavalkovsky24-May-16 9:00
memberEvgeny Zavalkovsky24-May-16 9:00 
AnswerRe: Performance degradation - you never delete locks from map Pin
Michael Chourdakis25-May-16 2:18
mvpMichael Chourdakis25-May-16 2:18 
QuestionDeadlock Pin
Evgeny Zavalkovsky24-May-16 8:45
memberEvgeny Zavalkovsky24-May-16 8:45 
AnswerRe: Deadlock Pin
Michael Chourdakis29-May-16 8:06
mvpMichael Chourdakis29-May-16 8:06 
GeneralRe: Deadlock Pin
Evgeny Zavalkovsky31-May-16 9:57
memberEvgeny Zavalkovsky31-May-16 9:57 
GeneralRe: Deadlock Pin
Michael Chourdakis22-Aug-16 20:58
mvpMichael Chourdakis22-Aug-16 20:58 
QuestionI don't believe this works Pin
Goran Mitrovic18-Dec-15 10:36
memberGoran Mitrovic18-Dec-15 10:36 
AnswerRe: I don't believe this works Pin
Michael Chourdakis18-Dec-15 10:42
memberMichael Chourdakis18-Dec-15 10:42 
GeneralRe: I don't believe this works Pin
Goran Mitrovic18-Dec-15 10:47
memberGoran Mitrovic18-Dec-15 10:47 
GeneralRe: I don't believe this works Pin
Michael Chourdakis18-Dec-15 22:11
memberMichael Chourdakis18-Dec-15 22:11 
GeneralRe: I don't believe this works Pin
William E. Kempf22-Dec-15 4:12
memberWilliam E. Kempf22-Dec-15 4:12 
GeneralRe: I don't believe this works Pin
Michael Chourdakis22-Dec-15 4:19
memberMichael Chourdakis22-Dec-15 4:19 
GeneralRe: I don't believe this works Pin
William E. Kempf22-Dec-15 6:22
memberWilliam E. Kempf22-Dec-15 6:22 
GeneralRe: I don't believe this works Pin
Michael Chourdakis22-Dec-15 6:25
memberMichael Chourdakis22-Dec-15 6:25 
GeneralRe: I don't believe this works Pin
William E. Kempf22-Dec-15 7:00
memberWilliam E. Kempf22-Dec-15 7:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web03-2016 | 2.8.180712.1 | Last Updated 10 Dec 2017
Article Copyright 2015 by Michael Chourdakis
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid