クリティカルセクションのみを使用したWin32読み取り/書き込みロック
-
06-07-2019 - |
質問
作業中のプロジェクトの一部としてWin32 APIを使用して、C ++で読み取り/書き込みロックを実装する必要があります。既存のソリューションはすべて、実行中にコンテキストスイッチを必要とするカーネルオブジェクト(セマフォとミューテックス)を使用します。これは私のアプリケーションにとっては遅すぎます。
可能であれば、クリティカルセクションのみを使用して実装してください。ロックはプロセスセーフである必要はなく、スレッドセーフのみです。これについてのアイデアはありますか?
解決
少なくとも1つのカーネルレベルオブジェクト(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
は、rdlockおよびrdunlock の -
rwlock-&gt; 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. */
writerLock
に置き換えられます
ただし、これにより公平性が失われるため、上記の解決策をお勧めします。
本をご覧ください&quot; Windowsでの同時プログラミング&quot ;リーダー/ライターロックのさまざまな参照例があります。
スレッドビルディングブロックのnoreferrer "> spin_rw_mutex ...
spin_rw_mutex
は厳密にユーザーランドにあります スピン待機を使用してブロックする
これは古い質問ですが、おそらく誰かがこれを役に立つと思うでしょう。高性能の Windows用のオープンソース RWLock
を開発しました。 Vista + SRWLock
言及されたマイケルを使用するか、そうでなければユーザースペースの実装にフォールバックします。
追加のボーナスとして、4つの異なる「フレーバー」があります。そのうちの1つ(ただし、これも最速の基本に固執することができます)、それぞれがより多くの同期オプションを提供します。これは、基本的な RWLock()
で始まります。これは、リエントラントではなく、単一プロセスの同期に限定され、再入可能な本格的なクロスプロセスIPC RWLockへの読み取り/書き込みロックのスワッピングはありませんサポートおよび読み取り/書き込みの脱昇。
前述のように、可能な限り最高のパフォーマンスを得るために、Vista +のスリムな読み取り/書き込みロックに動的にスワップアウトしますが、Windowsで完全に互換性のある実装にフォールバックするため、まったく心配する必要はありませんXPとその同類。
既にのみがミューテックスを使用するソリューションを知っている場合は、代わりにクリティカルセクションを使用するように修正できるはずです。
2つのクリティカルセクションといくつかのカウンターを使用して独自のロールを行いました。私たちのニーズに合っています-ライター数が非常に少なく、ライターがリーダーよりも優先されます、など。
ここに、私が思いつくことのできる最小のソリューションを示します。
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)で行をコメント化します。