Правильный способ синхронизации между методом и остановкой
У меня есть функция (давайте назовем ее функцией A), что от 0 до многих потоков может получить к ней доступ (в то же время нет общих ресурсов). В любой момент времени пользователь может использовать, чтобы остановить процесс. Функция останова должна гарантировать, что есть потоки, обращающиеся к функции A, чтобы можно было выполнить постепенное отключение. Есть ли родная процедура для этого?
То, что я собирался сделать, - это иметь InterlockedIncrement целое число каждый раз, когда вызывается функция A (и соответствующий InterlockedDecrement для указанного целого числа, когда функция A существует). Когда происходит InterlockedDecrement, он проверяет значение целого числа, если оно установлено равным нулю, событие получает сигнал. Если значение не равно нулю, событию присваивается значение "без сигнала".
Это имеет смысл в моем сознании, но мне любопытно, есть ли более приспособленная структура / функциональность, приспособленная для этого.
Я все еще должен сказать о том факте, что функция "стоп" может истощаться (в том смысле, что указанное целое число никогда не может быть установлено на ноль). Заметка: когда происходит событие остановки, процесс InterlockedIncrement должен быть остановлен, чтобы уменьшить указанное голодание.
1 ответ
То, что вам нужно и нужно реализовать, называется защитой от переполнения. к сожалению, он поддерживается только в режиме ядра, но не сложно реализовать его и в пользовательском режиме.
простейшая реализация следующая:
HANDLE ghStopEvent;
LONG gLockCount = 1;
BOOLEAN bStop = FALSE;
void unlock()
{
if (!InterlockedDecrement(&gLockCount)) SetEvent(ghStopEvent);
}
BOOL lock()
{
LONG Value = gLockCount, NewValue;
for ( ; !bStop && Value; Value = NewValue)
{
NewValue = InterlockedCompareExchange(&gLockCount, Value + 1, Value);
if (NewValue == Value) return TRUE;
}
return FALSE;
}
void funcA();
void UseA()
{
if (lock())
{
funcA();
unlock();
}
}
а когда захочешь начать краткое изложение - однажды позвони
bStop = TRUE; unlock();
как ты видишь lock
функция с инкрементной блокировкой gLockCount
на 1, но только если это не 0.
в режиме ядра вы можете позвонить вместо
EX_RUNDOWN_REF gRunRef;
void UseA()
{
if (ExAcquireRundownProtection(&gRunRef))
{
funcA();
ExReleaseRundownProtection(&gRunRef)
}
}
и на месте финала unlock
- ExWaitForRundownProtectionRelease
немного более сложная и масштабируемая реализация защиты от ранда:
#define RUNDOWN_INIT_VALUE 0x80000000
#define RUNDOWN_COMPLETE_VALUE 0
class __declspec(novtable) RUNDOWN_REF
{
LONG _LockCount;
protected:
virtual void RundownCompleted() = 0;
public:
BOOL IsRundownBegin()
{
return 0 <= _LockCount;
}
void Reinit()
{
if (InterlockedCompareExchange(&_LockCount, RUNDOWN_INIT_VALUE, RUNDOWN_COMPLETE_VALUE) != RUNDOWN_COMPLETE_VALUE)
{
__debugbreak();
}
}
RUNDOWN_REF()
{
_LockCount = RUNDOWN_INIT_VALUE;
}
BOOL AcquireRundownProtection()
{
LONG Value = _LockCount, NewValue;
for ( ; Value < 0; Value = NewValue)
{
NewValue = InterlockedCompareExchange(&_LockCount, Value + 1, Value);
if (NewValue == Value) return TRUE;
}
return FALSE;
}
void ReleaseRundownProtection()
{
if (RUNDOWN_COMPLETE_VALUE == InterlockedDecrement(&_LockCount))
{
RundownCompleted();
}
}
void BeginRundown()
{
if (AcquireRundownProtection())
{
_interlockedbittestandreset(&_LockCount, 31);
ReleaseRundownProtection();
}
}
};
и используйте это как:
class MY_RUNDOWN_REF : public RUNDOWN_REF
{
HANDLE _hEvent;
virtual void RundownCompleted()
{
SetEvent(_hEvent);
}
// ...
} gRunRef;
void UseA()
{
if (gRunRef.AcquireRundownProtection())
{
funcA();
gRunRef.ReleaseRundownProtection();
}
}
и когда вы хотите остановиться:
gRunRef.BeginRundown();// can be safe called multiple times
// wait on gRunRef._hEvent here
Интересно, что в ядре существует еще один (более старый - от win2000, когда защита от изнурения xp) api Remove Locks. это делает почти то же самое. отличается только внутренней реализацией и использованием. с кодом удаления блокировки будет выглядеть так:
IO_REMOVE_LOCK gLock;
void UseA()
{
if (0 <= IoAcquireRemoveLock(&gLock, 0))
{
funcA();
IoReleaseRemoveLock(&gLock, 0);
}
}
и когда мы хотим остановиться - звоните
IoAcquireRemoveLock(&gLock, 0);
IoReleaseRemoveLockAndWait(&gLock, 0);
мой первый код spinet при реализации рядом с удалением блокировок реализации, когда второй рядом с реализацией защиты от подробного описания. но по смыслу оба делают то же самое