multithreading - 对自旋锁感到困惑

标签 multithreading language-agnostic synchronization locking

我读自旋锁代码from here ,尤其是这部分

inline void Enter(void)
{
    int prev_s;
    do
    {
        prev_s = TestAndSet(&m_s, 0);
        if (m_s == 0 && prev_s == 1)
        {
            break;
        }
        // reluinquish current timeslice (can only
        // be used when OS available and
        // we do NOT want to 'spin')
        // HWSleep(0);
    }
    while (true);
}

为什么我们需要测试两个条件 m_s == 0 && prev_s == 1?我认为只需测试 prev_s == 1 就足够了。有什么想法吗?

编辑:版本 2。如果有错误,我们应该以这种方式修复吗?

inline void Enter(void)
{
    do
    {
        if (m_s == 0 && 1 == TestAndSet(&m_s, 0))
        {
            break;
        }
        // reluinquish current timeslice (can only
        // be used when OS available and
        // we do NOT want to 'spin')
        // HWSleep(0);
    }
    while (true);
}

编辑:版本 3。我认为功能级别的版本 3 是正确的,但性能不够好,因为每次我们都需要写入,没有提前读取测试。我的理解正确吗?

inline void Enter(void)
{
    do
    {
        if (1 == TestAndSet(&m_s, 0))
        {
            break;
        }
        // reluinquish current timeslice (can only
        // be used when OS available and
        // we do NOT want to 'spin')
        // HWSleep(0);
    }
    while (true);
}

@dragonfly,这是我的 bug 修复版本 4(修复了你指出的版本 2 中的一个 bug),请你看看它是否正确?谢谢!

编辑:版本 4。

inline void Enter(void)
{
    do
    {
        if (m_s == 1 && 1 == TestAndSet(&m_s, 0))
        {
            break;
        }
        // reluinquish current timeslice (can only
        // be used when OS available and
        // we do NOT want to 'spin')
        // HWSleep(0);
    }
    while (true);
}

最佳答案

在我看来,这是一次尝试优化的尝试,出了点小问题。我怀疑它正在尝试“TATAS”——“测试和测试和设置”,如果它看到锁已经被占用,它甚至不会尝试执行 TestAndSet。

post about spin locks for .NET 中, Joe Duffy 将此 TATAS 代码写为:

class SpinLock {
    private volatile int m_taken;

    public void Enter() {
        while (true) {
            if (m_taken == 0 &&
                    Interlocked.Exchange(ref m_taken, 1) == 0)
                break;
        }
    }

    public void Exit() {
        m_taken = 0;
    }
}

(请注意,Joe 使用 1 表示已锁定,使用 0 表示未锁定,这与代码项目示例不同 - 两者都可以,只是不要混淆两者!)

请注意,此处对 Interlocked.Exchange 的调用m_taken 为 0 为条件。这减少了争用——相对昂贵的(我猜)测试和设置操作被避免了,这是不必要的。我怀疑这就是作者的目的,但没有完全正确。

Wikipedia article about spinlocks 中也提到了这一点在“重大优化”下:

To reduce inter-CPU bus traffic, when the lock is not acquired, the code should loop reading without trying to write anything, until it reads a changed value. Because of MESI caching protocols, this causes the cache line for the lock to become "Shared"; then there is remarkably no bus traffic while a CPU is waiting for the lock. This optimization is effective on all CPU architectures that have a cache per CPU, because MESI is so ubiquitous.

“循环读取”正是 while 循环所做的 - 直到它看到 m_taken 发生变化,它才会读取。当它看到变化时(即当锁被释放时),它会再次锁定。

当然,我很可能遗漏了一些重要的东西 - 像这样的问题非常微妙。

关于multithreading - 对自旋锁感到困惑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/688785/

相关文章:

validation - 领域驱动设计 - 跨不同聚合的命令的复杂验证

java - 有效的重新排序 - 在新的 JMM 下

networking - Golang数组和 map 通过网络同步

java - 如何取消android studio中的特定线程?

python - 当线程等待 stdout 时继续运行 python 脚本

c - 多线程编程: Under what situation does the variable 'iget' is equal to the variable 'iput' ?

插件架构的安全/认证

language-agnostic - 这个通用算法的名称是什么?

python - 如何在工作线程中使用关闭句柄

java - 同步对象在 notifyAll() 之前未被线程锁定