如果我有这样的陈述怎么办:
private int sharedValue = 0;
public void SomeMethodOne()
{
lock(this){sharedValue++;}
}
public void SomeMethodTwo()
{
lock(this){sharedValue--;}
}
因此,对于一个进入锁的线程,它必须首先检查另一个线程是否在其上运行。如果不是,它可以进入并且必须向内存写入内容,这肯定不是原子的,因为它需要读写。
那么为什么一个线程无法读取锁定而另一个线程将其所有权写入其中呢?
简化为什么两个线程不能同时进入锁定状态?
答案 0 :(得分:2)
看起来你基本上都在询问锁是如何工作的。如果没有建立锁,锁如何以原子方式维护内部状态?看起来像chicken and egg problem起初不是吗?
神奇的一切都是因为compare-and-swap(CAS)操作而发生的。 CAS操作是一个硬件级指令,它执行两项重要操作。
在最基本的层面上,这就是诀窍的完成方式。并不是所有其他线程都被阻止阅读而另一个线程正在写入。这完全是错误的思考方式。实际发生的是所有线程同时充当写入者。该策略比悲观更乐观。每个线程都试图通过执行这种称为CAS的特殊写入来获取锁。实际上,您可以通过Interlocked.CompareExchange
(ICX)方法访问.NET中的CAS操作。每个同步原语都可以通过这个单一操作构建。
如果我要在C#中从头开始编写一个Monitor
类似的类(这是lock
关键字在幕后使用的那个),我可以使用Interlocked.CompareExchange
来完成方法。这是一个过于简化的实现。请记住,这肯定是而不是 .NET Framework如何实现它。 1 我提供以下代码的原因是为了向您展示如何可以在纯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;
}
}
此实现演示了一些重要的事情。
注意我在等待获取锁定时如何使用Thread.SpinWait
,Thread.Sleep(0)
,Thread.Sleep(1)
和Thread.Yield
。等待策略过于简化,但已经接近real life algorithm implemented in the BCL。我故意在上面的Enter
方法中保持代码简单,以便更容易发现关键位。这不是我通常会实现这一点的方式,但我希望它确实能带来重点。
另请注意,我上面的SimpleMonitor
存在很多问题。这里只有少数。
Wait
类的Pulse
或Monitor
方法。他们真的难以做对。 1 CLR实际上将使用每个引用类型上存在的特殊内存块。该存储器块称为“同步块”。 Monitor
将操纵此内存块中的位以获取和释放锁。此操作可能需要内核事件对象。您可以在Joe Duffy's blog上了解更多相关信息。
答案 1 :(得分:1)
lock
用于创建实际用于锁定的Monitor
对象。
您可以在此处详细了解Monitor
:http://msdn.microsoft.com/en-us/library/system.threading.monitor.aspx。 Enter
的{{1}}方法确保当时只有一个线程可以进入临界区:
顺便说一句,你应该避免锁定获取对象的锁定。这一行动也标志着一个关键部分的开始。没有其他线程可以进入临界区,除非它使用不同的锁定对象执行临界区中的指令。
Monitor
(this
)。您应该在类(静态或非静态)上使用私有变量来保护关键部分。您可以在上面提供的相同链接中阅读更多内容,但原因是:
选择要同步的对象时,应仅锁定私有或内部对象。锁定外部对象可能会导致死锁,因为不相关的代码可以选择相同的对象来锁定以用于不同的目的。