我读自旋锁代码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/