我要實現在C++中使用Win32 API作爲工作項目的一部分讀/寫鎖。所有現有的解決方案都使用內核對象(信號量和互斥量),這些對象在執行過程中需要上下文切換。這對我的應用來說太慢了。的Win32讀/寫鎖僅使用臨界區
我想實現一個只使用關鍵部分,如果可能的話。鎖不必是安全的,只有線程安全。有關如何去做這件事的任何想法?
我要實現在C++中使用Win32 API作爲工作項目的一部分讀/寫鎖。所有現有的解決方案都使用內核對象(信號量和互斥量),這些對象在執行過程中需要上下文切換。這對我的應用來說太慢了。的Win32讀/寫鎖僅使用臨界區
我想實現一個只使用關鍵部分,如果可能的話。鎖不必是安全的,只有線程安全。有關如何去做這件事的任何想法?
我不認爲這可以在不使用至少一個內核級的對象(互斥或信號)來完成的,因爲你需要的內核的幫助,使調用進程阻塞,直到鎖可用。
關鍵部分確實提供了阻塞,但API太有限。例如你不能抓住一個CS,發現有一個讀鎖可用,但不是寫鎖,並等待另一個進程完成讀取(因爲如果另一個進程有關鍵部分,它會阻止其他讀者出錯,如果它不那麼你的程序將不會阻止,但旋轉,燃燒的CPU週期。)
但是你可以做的是使用一個自旋鎖,並回落到一個互斥每當有爭。關鍵部分本身就是這樣實施的。我將採用現有的關鍵部分實現,並用單獨的讀取器&寫入器計數替換PID字段。
如果您已經知道只有使用互斥體的解決方案,那麼您應該可以將其修改爲使用臨界區域。
我們使用兩個關鍵部分和一些計數器來滾動我們自己。它適合我們的需求 - 我們的寫作者數量非常低,作者優先於讀者等。我無權發佈我們的內容,但可以說沒有互斥和信號量是可能的。
看看它有很多的讀/寫鎖不同的參考例子書「Concurrent Programming on Windows」。
退房的spin_rw_mutex從英特爾Thread Building Blocks ...
spin_rw_mutex
是嚴格的用戶,土地 並採用自旋等待阻塞
這裏是我能想出最小的解決方案:
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);
}
};
老問題,但是這是應該工作。它不會爭用。如果讀者很少或沒有爭用,讀者會承擔額外的有限費用,因爲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和rdunlock writerLock
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. */
然而,這在失去公平,所以我更喜歡上面的解決方案。
這是一個老問題,但也許有人會覺得這很有用。我們開發了一個高性能的open-source RWLock
for Windows,它自動使用Vista + SRWLock
Michael mentioned(如果可用)或以其他方式回退到用戶空間實施。
作爲一個額外的好處,它有四種不同的「風味」(儘管你可以堅持基本的,也是最快的),每個提供更多的同步選項。它從不可重入的基本RWLock()
開始,僅限於單進程同步,並且不會將讀/寫鎖交換到具有重入入支持和讀/寫降低功能的完整跨進程IPC RWLock。
如前所述,它們會動態替換爲Vista +超薄讀寫鎖,以儘可能獲得最佳性能,但您根本無需擔心,因爲它會回退到完全兼容的實現Windows XP及其同類。
看我在這裏實現:
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)。
問題是,使用標準互斥鎖,任何人都可以鎖定/解鎖它。對於關鍵部分來說這不是真的,這使我的解決方案無法正常工作。 – 2009-06-17 18:38:00
不清楚你的意思 - 只有擁有mutext的線程才能解鎖它。一旦互斥鎖被解鎖,任何人都可以鎖定它。同樣有關鍵部分。 – 2009-06-17 18:40:53
對不起,我的意思是一個信號量。我需要一個解決方案,任何人都可以減少信號量,這對於關鍵部分是不可能的。 – 2009-06-17 18:42:30