它看起来像你基本上是问如何锁工作。锁怎样才能以原子的方式保持内部状态而不必建立锁?它似乎是一个chicken and egg problem起初不是吗?
这一切都是因为compare-and-swap(CAS)的操作而发生的。 CAS操作是一个硬件级别的指令,它执行两个重要的事情。
- 它生成一个内存屏障,以便指令重新排序受到约束。
- 它将内存地址的内容与另一个值进行比较,如果它们相等,则将原始值替换为新值。它以原子的方式完成所有这些。
从最基本的层面来看,这就是技巧的完成。并非所有其他线程都在阅读时被阻止,而另一线程正在写入。这完全是错误的思考方式。实际发生的情况是所有线程都同时作为编写者。该策略比悲观更乐观。每个线程都试图通过执行称为CAS的特殊类型的写入来获取锁。您实际上可以通过Interlocked.CompareExchange
(ICX)方法在.NET中访问CAS操作。每一个同步原语都可以从这个单独的操作中建立。
如果我要在C#中完全从头开始编写类似Monitor
的类(即关键字lock
在幕后使用的),我可以使用Interlocked.CompareExchange
方法来完成。这是一个过度简化的实现。请记住,这当然是而不是 .NET Framework如何做到这一点。 我提供下面的代码的原因是向您展示如何在纯粹的C#代码中完成而不需要幕后的CLR魔术,以及它可能让您考虑微软如何实施它。
public class SimpleMonitor
{
private int m_LockState = 0;
public void Enter()
{
int iterations = 0;
while (!TryEnter())
{
if (iterations < 10) Thread.SpinWait(4 << iterations);
else if (iterations % 20 == 0) Thread.Sleep(1);
else if (iterations % 5 == 0) Thread.Sleep(0);
else Thread.Yield();
iterations++;
}
}
public void Exit()
{
if (!TryExit())
{
throw new SynchronizationLockException();
}
}
public bool TryEnter()
{
return Interlocked.CompareExchange(ref m_LockState, 1, 0) == 0;
}
public bool TryExit()
{
return Interlocked.CompareExchange(ref m_LockState, 0, 1) == 1;
}
}
该实现演示了一些重要的事情。
- 它显示了如何使用ICX操作来自动读取和写入锁定状态。
- 它显示了可能发生的等待情况。
注意我是如何使用Thread.SpinWait
,Thread.Sleep(0)
,Thread.Sleep(1)
和Thread.Yield
而锁等待被收购。等待策略过于简化,但它的确已经接近real life algorithm implemented in the BCL。我故意在上面的Enter
方法中保持代码简单,以便更容易发现关键位。这不是我通常会如此实施的方式,但我希望它能够将重点放在首位。
另请注意,我的SimpleMonitor
上面有很多问题。这只是少数。
- 它不处理嵌套锁定。
- 它不提供
Wait
或Pulse
方法像真正的Monitor
类。他们真的很难做对。
的CLR将实际使用的存储器的特殊块上的每个引用类型存在。这块内存被称为“同步块”。 Monitor
将操纵这块内存中的位来获取和释放锁。这个动作可能需要一个内核事件对象。你可以阅读关于Joe Duffy's blog的更多信息。
查看另一个类似的问题在这里:http://stackoverflow.com/questions/14758088/how-are-atomic-operations-implemented-at-a-hardware-level – blt
他们是原子..具体来说,他们是一个原子更新的同步块索引..这只是一个整数。 –
这是锁定的关键 - 以便您可以编写安全的并发线程使用的代码。 – Enigmativity