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

如果可能的话,我想仅使用关键部分来实现一个。锁不必是进程安全的,只需是线程安全的。关于如何解决这个问题有什么想法吗?

有帮助吗?

解决方案

我不认为如果不使用至少一个内核级对象(Mutex或Semaphore)就可以做到这一点,因为你需要内核的帮助来调用进程块直到锁可用。

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

但是,无论何时存在争用,您都可以使用自旋锁并回退到互斥锁。关键部分本身就是这样实现的。我将采用现有的关键部分实现,并用单独的读取器和放大器替换PID字段。作家很重要。

其他提示

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

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

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

老问题,但这应该有效。它不会引发争论。如果读者很少或没有争用,则只需承担有限的额外费用,因为 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);
}

您可以通过使用单个 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.  */
    

但是这样就失去了公平性,所以我更喜欢上面的解决方案。

请查看“ Windows上的并发编程”一书;它有许多不同的读写器锁定参考示例。

查看线程构建模块的noreferrer“> spin_rw_mutex ......

  

spin_rw_mutex 严格来说是用户土地   并使用spin-wait for blocking

这是一个老问题,但也许有人会觉得这很有用。我们自动开发了一个高性能的开源<代码> RWLock for Windows 使用Vista + SRWLock Michael提及(如果可用),或以其他方式回退到用户空间实现。

作为额外的奖励,有四种不同的“味道”。它(尽管你可以坚持基本的,也是最快的),每个都提供更多的同步选项。它从基本的 RWLock()开始,它是不可重入的,仅限于单进程同步,并且没有将读/写锁交换到完全成熟的跨进程IPC RWLock并重新进入支持和读/写降低。

如上所述,它们会动态更换为Vista +超薄读写锁,以便在可能的情况下获得最佳性能,但您根本不必担心这一点,因为它将回归到Windows上完全兼容的实现XP及其同类。

如果您已经知道使用互斥锁的解决方案,您应该能够修改它以使用关键部分。

我们使用两个关键部分和一些计数器来推广自己。它符合我们的需求 - 我们的作者数量非常少,作者优先于读者等等。我不能自由发表我们的作品,但可以说没有互斥和信号量就可以。

这是我能提出的最小解决方案:

http://www.baboonz.org/rwlock.php

并逐字粘贴:

/** A simple Reader/Writer Lock.

This RWL has no events - we rely solely on spinlocks and sleep() to yield control to other threads.
I don't know what the exact penalty is for using sleep vs events, but at least when there is no contention, we are basically
as fast as a critical section. This code is written for Windows, but it should be trivial to find the appropriate
equivalents on another OS.

**/
class TinyReaderWriterLock
{
public:
    volatile uint32 Main;
    static const uint32 WriteDesireBit = 0x80000000;

    void Noop( uint32 tick )
    {
        if ( ((tick + 1) & 0xfff) == 0 )     // Sleep after 4k cycles. Crude, but usually better than spinning indefinitely.
            Sleep(0);
    }

    TinyReaderWriterLock()                 { Main = 0; }
    ~TinyReaderWriterLock()                { ASSERT( Main == 0 ); }

    void EnterRead()
    {
        for ( uint32 tick = 0 ;; tick++ )
        {
            uint32 oldVal = Main;
            if ( (oldVal & WriteDesireBit) == 0 )
            {
                if ( InterlockedCompareExchange( (LONG*) &Main, oldVal + 1, oldVal ) == oldVal )
                    break;
            }
            Noop(tick);
        }
    }

    void EnterWrite()
    {
        for ( uint32 tick = 0 ;; tick++ )
        {
            if ( (tick & 0xfff) == 0 )                                     // Set the write-desire bit every 4k cycles (including cycle 0).
                _InterlockedOr( (LONG*) &Main, WriteDesireBit );

            uint32 oldVal = Main;
            if ( oldVal == WriteDesireBit )
            {
                if ( InterlockedCompareExchange( (LONG*) &Main, -1, WriteDesireBit ) == WriteDesireBit )
                    break;
            }
            Noop(tick);
        }
    }

    void LeaveRead()
    {
        ASSERT( Main != -1 );
        InterlockedDecrement( (LONG*) &Main );
    }
    void LeaveWrite()
    {
        ASSERT( Main == -1 );
        InterlockedIncrement( (LONG*) &Main );
    }
};

在这里查看我的实现:

https://github.com/coolsoftware/LockLib

VRWLock是一个实现单个编写器的C ++类 - 多个读者逻辑。

还要测试项目TestLock.sln。

UPD。以下是读者和作者的简单代码:

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 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);
    }
};

要执行旋转等待,请使用Sleep(0)注释行。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top