Правильный способ синхронизации между методом и остановкой

У меня есть функция (давайте назовем ее функцией 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 при реализации рядом с удалением блокировок реализации, когда второй рядом с реализацией защиты от подробного описания. но по смыслу оба делают то же самое

Другие вопросы по тегам