仅使用关键部分的Win32读/写锁定

Dan*_*enc 9 c++ winapi multithreading critical-section

我必须使用Win32 api在C++中实现读/写锁,作为工作项目的一部分.所有现有解决方案都使用在执行期间需要上下文切换的内核对象(信号量和互斥量).这对我的申请来说太慢了.

如果可能的话,我想只使用关键部分实现一个.锁不必是过程安全的,只有线程安全.关于如何解决这个问题的任何想法?

Mic*_*ael 12

如果您可以使用Vista或更高版本,则应使用内置的SRWLock.它们像关键部分一样轻量级,在没有争用时完全是用户模式.

Joe Duffy的博客最近有一些关于实现不同类型的非阻塞读取器/写入器锁的条目.这些锁会旋转,所以如果你打算在握住锁的同时做很多工作,它们就不合适了.代码是C#,但应该直接移植到本机.

您可以使用关键部分和事件实现读取器/写入器锁定 - 您只需要保持足够的状态以仅在必要时发出事件信号以避免不必要的内核模式调用.


fin*_*nnw 6

我不认为这可以在不使用至少一个内核级对象(Mutex或Semaphore)的情况下完成,因为您需要内核的帮助来调用进程块直到锁可用.

关键部分确实提供了阻止,但API太有限了.例如,您无法获取CS,发现读取锁定可用而不是写入锁定,并等待其他进程完成读取(因为如果其他进程具有关键部分,则会阻止其他读取器出错,如果它没有那么你的进程不会阻止但是旋转,燃烧CPU周期.)

但是,你可以做的是使用自旋锁,并在有争用时回退到互斥锁.关键部分本身就是这样实现的.我将采用现有的临界区实现,并将PID字段替换为单独的读写器计数.


Pao*_*ini 6

老问题,但这是应该工作的东西.它不会引发争论.如果读者很少或没有争用,读者会产生有限的额外费用,因为它SetEvent被称为懒惰(请查看没有此优化的更重量级版本的编辑历史记录).

#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);
}
Run Code Online (Sandbox Code Playgroud)

您可以通过使用单个来降低读者的成本CRITICAL_SECTION:

  • countsLock被替换为writerLock在rdlock和rdunlock

  • rwlock->waitingWriter = FALSE 在wrunlock被删除

  • wrlock的身体变成了

    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.  */
    
    Run Code Online (Sandbox Code Playgroud)

然而,这种损失是公平的,所以我更喜欢上述解决方案.