方法和停止功能之间的正确同步方式

Correct way of synchronization between a method and a stop functionality

我有一个函数(我们称之为函数 A),0 到许多线程可以访问它(同时,没有共享资源)。在任何给定时间,用户都可以使用停止进程。停止功能需要确保有线程访问函数 A,以便可以执行正常关闭。有这样做的本机程序吗?

我要做的是在每次调用函数 A 时都有一个 InterlockedIncrement 整数(当函数 A 存在时,在所述整数上有一个相应的 InterlockedDecrement)。当 InterlockedDecrement 发生时,它检查整数的值,如果它设置为零,则事件设置为发出信号。如果该值不为零,则事件设置为未发出信号。

这在我看来是有道理的,但我很好奇是否有更适合这样做的原生结构/功能。

我仍然不得不考虑 "stop" 函数可能会被饿死的事实(从某种意义上说,所述整数可能永远不会设置为零)。旁注:当停止事件发生时,应停止 InterlockedIncrement 进程,以减少所述饥饿。

您需要和想要的工具叫做 Run-Down Protection。不幸的是,它只支持内核模式,但在用户模式下也很难自己实现。

接下来是最简单的实现:

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 函数是在 1 上互锁递增 gLockCount 但前提是它不是 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,当 rundown protection 来自 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 通过实现接近删除锁实现,当第二个接近破损保护实现时。但从感觉上来说两者都是一样的