当前位置:  开发笔记 > 编程语言 > 正文

对旋转锁感到困惑

如何解决《对旋转锁感到困惑》经验,为你挑选了1个好方法。

我从这里读取旋转锁定代码,特别是这部分

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,这是我的错误修复版本4(修正了你指出的版本2中的错误),你能检查它是否正确吗?谢谢!

编辑:版本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);
}

Jon Skeet.. 7

在我看来,这是一次尝试优化略有错误.我怀疑它正在尝试"TATAS" - "测试和测试和设置",如果它可以看到已经采取锁定,它甚至不会尝试执行TestAndSet.

在关于.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的调用是以0 为条件的m_taken.这减少了争用 - 在没有必要的情况下避免了相对昂贵的(我猜)测试和设置操作.我怀疑这是作者的目标,但并没有完全正确.

维基百科关于 "重大优化"下的自旋锁的文章中也提到了这一点:

为了减少CPU间总线流量,当未获取锁定时,代码应循环读取而不尝试写入任何内容,直到它读取更改的值.由于MESI缓存协议,这导致锁的缓存行变为"共享"; 然后,当CPU等待锁定时,总是没有总线流量.这种优化对于每个CPU都有缓存的所有CPU架构都是有效的,因为MESI无处不在.

那个"循环读取"正是while循环所做的 - 直到它看到m_taken变化,它才会读取.当它看到变化时(即当锁定被释放时)它又有另一个锁定.

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



1> Jon Skeet..:

在我看来,这是一次尝试优化略有错误.我怀疑它正在尝试"TATAS" - "测试和测试和设置",如果它可以看到已经采取锁定,它甚至不会尝试执行TestAndSet.

在关于.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的调用是以0 为条件的m_taken.这减少了争用 - 在没有必要的情况下避免了相对昂贵的(我猜)测试和设置操作.我怀疑这是作者的目标,但并没有完全正确.

维基百科关于 "重大优化"下的自旋锁的文章中也提到了这一点:

为了减少CPU间总线流量,当未获取锁定时,代码应循环读取而不尝试写入任何内容,直到它读取更改的值.由于MESI缓存协议,这导致锁的缓存行变为"共享"; 然后,当CPU等待锁定时,总是没有总线流量.这种优化对于每个CPU都有缓存的所有CPU架构都是有效的,因为MESI无处不在.

那个"循环读取"正是while循环所做的 - 直到它看到m_taken变化,它才会读取.当它看到变化时(即当锁定被释放时)它又有另一个锁定.

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

推荐阅读
落单鸟人
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有