仅使用关键部分的 Win32 读/写锁定
-
06-07-2019 - |
题
作为工作项目的一部分,我必须使用 Win32 api 在 C++ 中实现读/写锁。所有现有的解决方案都使用需要在执行期间进行上下文切换的内核对象(信号量和互斥体)。这对于我的应用程序来说太慢了。
如果可能的话,我想仅使用关键部分来实现一个。锁不必是进程安全的,只需是线程安全的。关于如何解决这个问题有什么想法吗?
解决方案
我不认为如果不使用至少一个内核级对象(Mutex或Semaphore)就可以做到这一点,因为你需要内核的帮助来调用进程块直到锁可用。
关键部分确实提供了阻止,但API太有限了。例如您无法获取CS,发现读锁定可用而不是写锁定,并等待其他进程完成读取(因为如果其他进程具有关键部分,则会阻止其他读取错误,如果有然后,你的进程不会阻塞,但会旋转,燃烧CPU周期。)
但是,无论何时存在争用,您都可以使用自旋锁并回退到互斥锁。关键部分本身就是这样实现的。我将采用现有的关键部分实现,并用单独的读取器和放大器替换PID字段。作家很重要。
其他提示
老问题,但这应该有效。它不会引发争论。如果读者很少或没有争用,则只需承担有限的额外费用,因为 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)注释行。