VC++中没有互斥锁的Spinlock同步.h

Spinlock sync in VC++ without mutex.h

本文关键字:Spinlock 同步 VC++      更新时间:2023-10-16

我有点迷路了。我相信我在这里拥有的是绝对线程安全的,但我不确定它是否是。我需要一个非常快速的旋转锁机制,用于更复杂的锁的小部分,以优化线程锁定和释放(我需要制作一个更大的锁,它总是按线程被阻止的顺序释放线程-这在互斥体/EnterCriticalSection中是不会发生的)。然而,当谈到制作最小、最快的锁时,我真的不知道该怎么办。

class SpinGate
{
private:
    volatile LONGLONG key = 0;
    volatile LONGLONG gate = 1;
public:
    void EnterGate();
    void ExitGate();
};
void SpinGate::EnterGate()
{
    LONGLONG myKey = InterlockedAdd64(&key, 1);
    while (myKey != gate) {}
}
void SpinGate::ExitGate()
{
    InterlockedAdd64(&gate, 1);
}

认为我构建的这个东西将确保,即使1000000000个线程试图同时获得一个密钥,他们也会获得不同的密钥,因此被迫旋转,直到他们的密钥出现。但是,当谈到C++时,在没有标准库对象的情况下实现对内存的安全读写的机制在某种程度上超出了我的知识范围。

我很好奇像"interkedadd64"这样的函数是如何同时执行两个操作的,一个是增量,然后是读取,同时阻塞其他线程。无论这是否始终线程安全。

我认为我构建的这个东西将确保,即使1000000000个线程试图同时获得一个密钥,它们也会获得不同的密钥,因此被迫旋转,直到它们的密钥出现。

没有。C++volatile关键字不提供任何线程间保证。

我很好奇像"interkedadd64"这样的函数是如何同时执行两个操作的,一个是增量,然后是读取,同时阻塞其他线程。不管这是否总是线程安全的。

它不会阻塞其他线程。在现代CPU上,它只是在原子操作的持续时间内锁定缓存线,这样在读取和写入之间就没有其他内核可以访问该内存位置。

免责声明。我真的不鼓励开发人员编写自己的锁机制。大多数自定义锁实现都无法通过以下任何测试:

  1. 很少有自制的实现试图保证有序的公平性。(一个以较高优先级运行的线程可能会使另一个线程无法获得锁)。

  2. 很难做到正确。更难维护。当你有虫子的时候,你会把头撞在墙上的。

  3. 当一个简单的Windows CriticalSection锁就足够了时,实际上并不需要。

但既然OP问了,我就试试看。你可能喜欢它。其他人可能会对它吹毛求疵。

在我的实现中,我添加了一些额外的工作,以允许同一线程递归地获取锁。我想你可以把这个拿出来,然后没事。

class SpinGate
{
private:
    DWORD _dwOwnerThread;
    LONG _ownerCount;
public:
    SpinGate()
    {
        _dwOwnerThread = 0;
        _ownerCount = 0;
    }
    void EnterGate()
    {
        LONG currentThread = (LONG)GetCurrentThreadId();
        LONG result = 0;
        bool owned = false;
        // if we own the lock, just increment it
        result = InterlockedCompareExchange(&_dwOwnerThread, currentThread, currentThread);
        if (result == currentThread)
        {
            // we own the lock, so increment and exit
            _ownerCount++;
            owned = true;
        }
        // otherwise, spin
        while (owned == false)
        {
            result = InterlockedCompareExchange(&_dwOwnerThread, currentThread, 0);
            if (result == 0)
            {
                _ownerCount = 1;
                owned = true;
            }
            else
            {
                while (_dwOwnerThread != 0)
                {
                    __asm
                    {
                        pause;
                    }
                }
            }
        }
        return;
    }
    void ExitGate()
    {
        LONG currentThread = (LONG)GetCurrentThreadId();
        LONG result = 0;
        // if we don't own the lock, this is a developer error!
        result = InterlockedCompareExchange(&_dwOwnerThread, currentThread, currentThread);
        if (result != currentThread)
        {
            // ERROR - Caller attempted to exit a gate he didn't own
            // ASSERT(FALSE);
            return;
        }
        _ownerCount--;
        if (_ownerCount == 0)
        {
            // give up the lock
            result = InterlockedCompareExchange(&_dwOwnerThread, 0, currentThread);
        }
        return;
    }
};