Win32 Read/Write Lock Using Only Critical Sections

前端 未结 10 1318
攒了一身酷
攒了一身酷 2020-12-14 12:20

I have to implement a read/write lock in C++ using the Win32 api as part of a project at work. All of the existing solutions use kernel objects (semaphores and mutexes) tha

相关标签:
10条回答
  • 2020-12-14 12:54

    Old question, but this is something that should work. It doesn't spin on contention. Readers incur limited extra cost if they have little or no contention, because SetEvent is called lazily (look at the edit history for a more heavyweight version that doesn't have this optimization).

    #include <windows.h>
    
    typedef struct _RW_LOCK {
        CRITICAL_SECTION countsLock;
        CRITICAL_SECTION writerLock;
        HANDLE noReaders;
        int readerCount;
        BOOL waitingWriter;
    } RW_LOCK, *PRW_LOCK;
    
    void rwlock_init(PRW_LOCK rwlock)
    {
        InitializeCriticalSection(&rwlock->writerLock);
        InitializeCriticalSection(&rwlock->countsLock);
    
        /*
         * Could use a semaphore as well.  There can only be one waiter ever,
         * so I'm showing an auto-reset event here.
         */
        rwlock->noReaders = CreateEvent (NULL, FALSE, FALSE, NULL);
    }
    
    void rwlock_rdlock(PRW_LOCK rwlock)
    {
        /*
         * We need to lock the writerLock too, otherwise a writer could
         * do the whole of rwlock_wrlock after the readerCount changed
         * from 0 to 1, but before the event was reset.
         */
        EnterCriticalSection(&rwlock->writerLock);
        EnterCriticalSection(&rwlock->countsLock);
        ++rwlock->readerCount;
        LeaveCriticalSection(&rwlock->countsLock);
        LeaveCriticalSection(&rwlock->writerLock);
    }
    
    int rwlock_wrlock(PRW_LOCK rwlock)
    {
        EnterCriticalSection(&rwlock->writerLock);
        /*
         * readerCount cannot become non-zero within the writerLock CS,
         * but it can become zero...
         */
        if (rwlock->readerCount > 0) {
            EnterCriticalSection(&rwlock->countsLock);
    
            /* ... so test it again.  */
            if (rwlock->readerCount > 0) {
                rwlock->waitingWriter = TRUE;
                LeaveCriticalSection(&rwlock->countsLock);
                WaitForSingleObject(rwlock->noReaders, INFINITE);
            } else {
                /* How lucky, no need to wait.  */
                LeaveCriticalSection(&rwlock->countsLock);
            }
        }
    
        /* writerLock remains locked.  */
    }
    
    void rwlock_rdunlock(PRW_LOCK rwlock)
    {
        EnterCriticalSection(&rwlock->countsLock);
        assert (rwlock->readerCount > 0);
        if (--rwlock->readerCount == 0) {
            if (rwlock->waitingWriter) {
                /*
                 * Clear waitingWriter here to avoid taking countsLock
                 * again in wrlock.
                 */
                rwlock->waitingWriter = FALSE;
                SetEvent(rwlock->noReaders);
            }
        }
        LeaveCriticalSection(&rwlock->countsLock);
    }
    
    void rwlock_wrunlock(PRW_LOCK rwlock)
    {
        LeaveCriticalSection(&rwlock->writerLock);
    }
    

    You could decrease the cost for readers by using a single CRITICAL_SECTION:

    • countsLock is replaced with writerLock in rdlock and rdunlock

    • rwlock->waitingWriter = FALSE is removed in wrunlock

    • wrlock's body is changed to

      EnterCriticalSection(&rwlock->writerLock);
      rwlock->waitingWriter = TRUE;
      while (rwlock->readerCount > 0) {
          LeaveCriticalSection(&rwlock->writerLock);
          WaitForSingleObject(rwlock->noReaders, INFINITE);
          EnterCriticalSection(&rwlock->writerLock);
      }
      rwlock->waitingWriter = FALSE;
      
      /* writerLock remains locked.  */
      

    However this loses in fairness, so I prefer the above solution.

    0 讨论(0)
  • 2020-12-14 12:55

    I wrote the following code using only critical sections.

    class ReadWriteLock {
        volatile LONG writelockcount;
        volatile LONG readlockcount;
        CRITICAL_SECTION cs;
    public:
        ReadWriteLock() {
            InitializeCriticalSection(&cs);
            writelockcount = 0;
            readlockcount = 0;
        }
        ~ReadWriteLock() {
            DeleteCriticalSection(&cs);
        }
        void AcquireReaderLock() {        
        retry:
            while (writelockcount) {
                Sleep(0);
            }
            EnterCriticalSection(&cs);
            if (!writelockcount) {
                readlockcount++;
            }
            else {
                LeaveCriticalSection(&cs);
                goto retry;
            }
            LeaveCriticalSection(&cs);
        }
        void ReleaseReaderLock() {
            EnterCriticalSection(&cs);
            readlockcount--;
            LeaveCriticalSection(&cs);
        }
        void AcquireWriterLock() {
            retry:
            while (writelockcount||readlockcount) {
                Sleep(0);
            }
            EnterCriticalSection(&cs);
            if (!writelockcount&&!readlockcount) {
                writelockcount++;
            }
            else {
                LeaveCriticalSection(&cs);
                goto retry;
            }
            LeaveCriticalSection(&cs);
        }
        void ReleaseWriterLock() {
            EnterCriticalSection(&cs);
            writelockcount--;
            LeaveCriticalSection(&cs);
        }
    };
    

    To perform a spin-wait, comment the lines with Sleep(0).

    0 讨论(0)
  • 2020-12-14 12:57

    Look my implementation here:

    https://github.com/coolsoftware/LockLib

    VRWLock is a C++ class that implements single writer - multiple readers logic.

    Look also test project TestLock.sln.

    UPD. Below is the simple code for reader and writer:

    LONG gCounter = 0;
    
    // reader
    
    for (;;) //loop
    {
      LONG n = InterlockedIncrement(&gCounter); 
      // n = value of gCounter after increment
      if (n <= MAX_READERS) break; // writer does not write anything - we can read
      InterlockedDecrement(&gCounter);
    }
    // read data here
    InterlockedDecrement(&gCounter); // release reader
    
    // writer
    
    for (;;) //loop
    {
      LONG n = InterlockedCompareExchange(&gCounter, (MAX_READERS+1), 0); 
      // n = value of gCounter before attempt to replace it by MAX_READERS+1 in InterlockedCompareExchange
      // if gCounter was 0 - no readers/writers and in gCounter will be MAX_READERS+1
      // if gCounter was not 0 - gCounter stays unchanged
      if (n == 0) break;
    }
    // write data here
    InterlockedExchangeAdd(&gCounter, -(MAX_READERS+1)); // release writer
    

    VRWLock class supports spin count and thread-specific reference count that allows to release locks of terminated threads.

    0 讨论(0)
  • 2020-12-14 12:58

    If you already know of a solution that only uses mutexes, you should be able to modify it to use critical sections instead.

    We rolled our own using two critical sections and some counters. It suits our needs - we have a very low writer count, writers get precedence over readers, etc. I'm not at liberty to publish ours but can say that it is possible without mutexes and semaphores.

    0 讨论(0)
提交回复
热议问题