我必须使用Win32 api在C ++中实现读/写锁,作为工作项目的一部分。所有现有解决方案都使用在执行期间需要上下文切换的内核对象(信号量和互斥量)。这对我的申请来说太慢了。
如果可能的话,我想只使用关键部分实现一个。锁不必是过程安全的,只有线程安全。关于如何解决这个问题的任何想法?
答案 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)
答案 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)注释行。