关于c#:锁是如何工作的?

How does lock work exactly?

我看到,对于使用非线程安全的对象,我们使用这样的锁包装代码:

1
2
3
4
5
6
private static readonly Object obj = new Object();

lock (obj)
{
    // thread unsafe code
}

所以当多个线程访问同一代码时会发生什么(假设它在ASP.NET Web应用程序中运行)。他们在排队吗?如果是,他们会等多久?

使用锁会对性能产生什么影响?


lock语句由c 3.0翻译为以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
var temp = obj;

Monitor.Enter(temp);

try
{
    // body
}
finally
{
    Monitor.Exit(temp);
}

在C 4.0中,这一点已经改变,现在生成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool lockWasTaken = false;
var temp = obj;
try
{
    Monitor.Enter(temp, ref lockWasTaken);
    // body
}
finally
{
    if (lockWasTaken)
    {
        Monitor.Exit(temp);
    }
}

你可以在这里找到更多关于Monitor.Enter所做工作的信息。引用MSDN:

Use Enter to acquire the Monitor on
the object passed as the parameter. If
another thread has executed an Enter
on the object but has not yet executed
the corresponding Exit, the current
thread will block until the other
thread releases the object. It is
legal for the same thread to invoke
Enter more than once without it
blocking; however, an equal number of
Exit calls must be invoked before
other threads waiting on the object
will unblock.

Monitor.Enter方法将无限等待,不会超时。


比你想象的简单。

据微软称:lock关键字确保一个线程不进入代码的关键部分,而另一个线程在关键部分。如果另一个线程试图输入锁定的代码,它将等待、阻止,直到对象被释放。

lock关键字在块的开头调用Enter,在块的结尾调用Exitlock关键字实际上处理后端的Monitor类。

例如:

1
2
3
4
5
6
private static readonly Object obj = new Object();

lock (obj)
{
    // critical section
}

在上面的代码中,首先线程进入一个关键部分,然后它将锁定obj。当另一个线程试图进入时,它还将尝试锁定已被第一个线程锁定的obj。我必须等待第一个线程释放obj。当第一个线程离开时,另一个线程将锁定obj,并进入关键部分。


不,他们没有排队,他们在睡觉

窗体的锁语句

1
lock (x) ...

其中x是引用类型的表达式,精确等于

1
2
3
4
var temp = x;
System.Threading.Monitor.Enter(temp);
try { ... }
finally { System.Threading.Monitor.Exit(temp); }

您只需要知道它们正在等待对方,并且只有一个线程将进入锁块,其他线程将等待…

监视器完全写在.NET中,因此足够快,还可以查看带有反射镜的类监视器以了解更多详细信息。


锁将阻止其他线程执行锁块中包含的代码。线程必须等待锁块内的线程完成并释放锁。这确实会对多线程环境中的性能产生负面影响。如果您确实需要这样做,您应该确保锁块中的代码可以很快地处理。您应该尽量避免昂贵的活动,如访问数据库等。


性能影响取决于锁定的方式。你可以在这里找到一个很好的优化列表:http://www.thinkingparallel.com/2007/07/31/10-ways-to-reduce-lock-competition-in-threaded-programs/

基本上,你应该尽量少锁,因为它会让你的等待代码进入睡眠状态。如果在锁中有一些繁重的计算或持久的代码(例如文件上传),则会导致巨大的性能损失。


lock语句转换为对MonitorEnterExit方法的调用。

lock语句将无限期地等待释放锁定对象。


lock语句中的部分只能由一个线程执行,因此所有其他线程都将无限期地等待该部分完成:持有锁的线程。这会导致所谓的死锁。


锁实际上是隐藏的监视器类。