我从这里读取旋转锁定代码,特别是这部分
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
变化,它才会读取.当它看到变化时(即当锁定被释放时)它又有另一个锁定.
当然,我很可能错过了一些重要的东西 - 像这样的问题非常微妙.
在我看来,这是一次尝试优化略有错误.我怀疑它正在尝试"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
变化,它才会读取.当它看到变化时(即当锁定被释放时)它又有另一个锁定.
当然,我很可能错过了一些重要的东西 - 像这样的问题非常微妙.