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

时间:2009-06-17 18:17:19

标签: c++ multithreading winapi critical-section

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

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

10 个答案:

答案 0 :(得分:11)

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

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

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

答案 1 :(得分:6)

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

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

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

答案 2 :(得分: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);
}

您可以使用单个CRITICAL_SECTION来降低读者的费用:

  • countsLock已替换为rdlock中的writerLock和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.  */
    

然而,这种公平性会失败,所以我更喜欢上述解决方案。

答案 3 :(得分:3)

看一下“Concurrent Programming on Windows”这本书,它有很多不同的读者/作家锁参考例。

答案 4 :(得分:3)

查看英特尔spin_rw_mutex ...

中的Thread Building Blocks
  

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

答案 5 :(得分:1)

这是一个老问题,但也许有人会觉得这很有用。我们开发了一个高性能open-source RWLock for Windows,如果可用,会自动使用Vista + SRWLock Michael mentioned,否则会回退到用户空间实现。

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

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

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

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

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

答案 8 :(得分:0)

在这里查看我的实现:

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类支持旋转计数和特定于线程的引用计数,允许释放已终止线程的锁。

答案 9 :(得分:0)

我仅使用关键部分编写了以下代码。

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)注释行。