Есть ли способ синхронизировать это без блокировок?

Скажем, у меня есть 3 функции, которые могут быть вызваны верхним уровнем:

  • Start — Будет вызван, только если мы еще не были запущены, или ранее был вызван Stop
  • Stop — Будет вызван только после успешного вызова на старт
  • Process — Может быть вызван в любой время (одновременно на разных потоках); если начнется, вызовет в нижний уровень

В Stopнадо всех ждать Process звонки, чтобы закончить звонить на нижний уровень и предотвратить любые дальнейшие звонки. С механизмом блокировки я могу придумать следующий псевдокод:

Start() {
ResetEvent(&StopCompleteEvent);
IsStarted = true;
RefCount = 0;
}

Stop() {
AcquireLock();
IsStarted = false;
WaitForCompletionEvent = (RefCount != 0);
ReleaseLock();
if (WaitForCompletionEvent)
WaitForEvent(&StopCompleteEvent);
ASSERT(RefCount == 0);
}

Process() {
AcquireLock();
AddedRef = IsStarted;
if (AddedRef)
RefCount++;
ReleaseLock();

if (!AddedRef) return;

ProcessLowerLayer();

AcquireLock();
FireCompletionEvent = (--RefCount == 0);
ReleaseLock();
if (FilreCompletionEvent)
SetEvent(&StopCompleteEvent);
}

Есть ли способ добиться такого же поведения без механизма блокировки? Возможно, с каким-то необычным использованием InterlockedCompareExchange и InterlockedIncremenet / InterlockedDecrement?

Причина, по которой я спрашиваю, заключается в том, что это путь к данным сетевого драйвера, и я бы предпочел не иметь никаких блокировок.

-1

Решение

Я считаю, что можно избежать использования явных блокировок и любых ненужных блокировок или вызовов ядра.

Обратите внимание, что это только псевдокод, для иллюстративных целей; он не видел компилятор. И хотя я считаю, что логика потоков является разумной, пожалуйста, проверьте ее правильность самостоятельно или обратитесь к эксперту, чтобы подтвердить ее; программирование без блокировки жесткий.

#define STOPPING 0x20000000;
#define STOPPED 0x40000000;
volatile LONG s = STOPPED;
// state and count
// bit 30 set -> stopped
// bit 29 set -> stopping
// bits 0 through 28 -> thread count

Start()
{
KeClearEvent(&StopCompleteEvent);
LONG n = InterlockedExchange(&s, 0);  // sets s to 0
if ((n & STOPPED) == 0)
bluescreen("Invalid call to Start()");
}

Stop()
{
LONG n = InterlockedCompareExchange(&s, STOPPED, 0);
if (n == 0)
{
// No calls to Process() were running so we could jump directly to stopped.
// Mission accomplished!
return;
}

LONG n = InterlockedOr(&s, STOPPING);
if ((n & STOPPED) != 0)
bluescreen("Stop called when already stopped");
if ((n & STOPPING) != 0)
bluescreen("Stop called when already stopping");

n = InterlockedCompareExchange(&s, STOPPED, STOPPING);
if (n == STOPPING)
{
// The last call to Process() exited before we set the STOPPING flag.
// Mission accomplished!
return;
}

// Now that STOPPING mode is set, and we know at least one call to Process
// is running, all we need do is wait for the event to be signaled.

KeWaitForSingleObject(...);

// The event is only ever signaled after a thread has successfully
// changed the state to STOPPED.  Mission accomplished!

return;
}

Process()
{
LONG n = InterlockedCompareExchange(&s, STOPPED, STOPPING);
if (n == STOPPING)
{
// We've just stopped; let the call to Stop() complete.
KeSetEvent(&StopCompleteEvent);
return;
}
if ((n & STOPPED) != 0 || (n & STOPPING) != 0)
{
// Checking here avoids changing the state unnecessarily when
// we already know we can't enter the lower layer.

// It also ensures that the transition from STOPPING to STOPPED can't
// be delayed even if there are lots of threads making new calls to Process().

return;
}

n = InterlockedIncrement(&s);
if ((n & STOPPED) != 0)
{
// Turns out we've just stopped, so the call to Process() must be aborted.

// Explicitly set the state back to STOPPED, rather than decrementing it,
// in case Start() has been called.  At least one thread will succeed.
InterlockedCompareExchange(&s, STOPPED, n);
return;
}

if ((n & STOPPING) == 0)
{
ProcessLowerLayer();
}

n = InterlockedDecrement(&s);
if ((n & STOPPED) != 0 || n == (STOPPED - 1))
bluescreen("Stopped during call to Process, shouldn't be possible!");

if (n != STOPPING)
return;

// Stop() has been called, and it looks like we're the last
// running call to Process() in which case we need to change the
// status to STOPPED and signal the call to Stop() to exit.

// However, another thread might have beaten us to it, so we must
// check again.  The event MUST only be set once per call to Stop().

n = InterlockedCompareExchange(&s, STOPPED, STOPPING);
if (n == STOPPING)
{
// We've just stopped; let the call to Stop() complete.
KeSetEvent(&StopCompleteEvent);
}
return;
}
2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]