Win32 Read/Write Lock Using Only Critical Sections

前端 未结 10 1338
攒了一身酷
攒了一身酷 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 
    
    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.

提交回复
热议问题