lock 语句在幕后做了啥?
Posted
技术标签:
【中文标题】lock 语句在幕后做了啥?【英文标题】:What does a lock statement do under the hood?lock 语句在幕后做了什么? 【发布时间】:2011-08-27 03:40:08 【问题描述】:我看到,为了使用不是线程安全的对象,我们用这样的锁来包装代码:
private static readonly Object obj = new Object();
lock (obj)
// thread unsafe code
那么,当多个线程访问相同的代码时会发生什么(假设它在 ASP.NET Web 应用程序中运行)。他们在排队吗?如果是,他们会等多久?
使用锁对性能有何影响?
【问题讨论】:
^ 死链接,请参阅:jonskeet.uk/csharp/threads/index.html 【参考方案1】:lock
语句由 C# 3.0 翻译成以下内容:
var temp = obj;
Monitor.Enter(temp);
try
// body
finally
Monitor.Exit(temp);
在 C# 4.0 this has changed 中,现在生成如下:
bool lockWasTaken = false;
var temp = obj;
try
Monitor.Enter(temp, ref lockWasTaken);
// body
finally
if (lockWasTaken)
Monitor.Exit(temp);
您可以找到更多关于Monitor.Enter
做什么here 的信息。引用 MSDN:
使用
Enter
获取监视器 作为参数传递的对象。如果 另一个线程执行了Enter
在对象上但尚未执行 对应的Exit
,当前 线程将阻塞,直到另一个 线程释放对象。它是 同一个线程调用是合法的Enter
不止一次没有它 阻塞;然而,相同数量的Exit
调用必须在之前调用 等待对象的其他线程 将解锁。
Monitor.Enter
方法会无限等待;它不会超时。
【讨论】:
根据 MSDN “使用 lock (C#) 或 SyncLock (Visual Basic) 关键字通常优于直接使用 Monitor 类,因为 lock 或 SyncLock 更简洁,并且因为 lock 或 SyncLock 确保即使受保护的代码抛出异常,也会释放底层监视器。这是通过 finally 关键字完成的,无论是否抛出异常,它都会执行其关联的代码块。 msdn.microsoft.com/en-us/library/ms173179.aspx var temp = obj; 的意义何在?线。既然一开始只是一个 ref,那么再做一个有什么好处呢? @priehl 它允许用户更改obj
而不会导致整个系统死锁。
@Joymon 最终,每个语言特性都是语法糖。语言功能旨在提高开发人员的工作效率并提高应用程序的可维护性,锁定功能也是如此。
正确。这就是lock
-statement 和 Monitor 的全部目的:这样您就可以在一个线程中执行操作,而不必担心另一个线程会搞砸它。【参考方案2】:
它比你想象的要简单-
根据Microsoft:
lock
关键字确保一个线程不进入代码的临界区,而另一个线程在临界区。如果另一个线程试图输入一个锁定的代码,它会等待,阻塞,直到对象被释放。
lock
关键字在块的开头调用 Enter
,在块的末尾调用 Exit
。 lock
关键字实际上在后端处理 Monitor
类。
例如:
private static readonly Object obj = new Object();
lock (obj)
// critical section
在上面的代码中,线程首先进入临界区,然后它会锁定obj
。当另一个线程试图进入时,它也会尝试锁定obj
,而obj
已经被第一个线程锁定了。第二个线程必须等待第一个线程释放obj
。当第一个线程离开时,另一个线程将锁定obj
并进入临界区。
【讨论】:
我们应该创建一个虚拟对象来锁定还是我们可以锁定上下文中的现有变量? @batmaci - 锁定一个单独的私有虚拟对象可以保证没有其他人锁定该对象。如果您锁定数据并且同一条数据对外部可见,则您将失去该保证。 如果有多个进程在等待释放锁会怎样?等待进程是否排队以便它们将按 FIFO 顺序锁定临界区? @jstuardo - 它们已排队,但不能保证顺序是 FIFO。查看此链接:albahari.com/threading/part2.aspx 从net-informations.com/faq/qk/lock.htm复制没有署名【参考方案3】:不,他们没有排队,他们正在睡觉
形式的锁语句
lock (x) ...
其中 x 是引用类型的表达式,完全等价于
var temp = x;
System.Threading.Monitor.Enter(temp);
try ...
finally System.Threading.Monitor.Exit(temp);
你只需要知道他们在互相等待,只有一个线程会进入锁块,其他的会等待......
Monitor 完全用 .net 编写,因此速度足够快,更多详情请查看 class Monitor 和 reflector
【讨论】:
请注意,为lock
语句发出的代码在 C#4 中略有变化:blogs.msdn.com/b/ericlippert/archive/2009/03/06/…
@ArsenMkrt,他们没有保持在“阻塞”状态“队列?。我认为睡眠和阻塞状态有一些区别,不是吗?
你的意思是@Mohanavel 有什么区别?
这不是问题所在。问题是关于“锁定”关键字。假设一个进程进入一个“锁定”部分。这意味着该进程会阻塞那段代码,并且在该锁被释放之前,其他进程可能无法进入该部分。好吧....现在,又有 2 个进程尝试进入同一个块。由于它受到“lock”关键字的保护,他们会等待,根据这个论坛的说法。当第一个进程释放锁时。什么进程进入块?第一个尝试进入的还是最后一个?
我猜你的意思是线程而不是进程...如果是这样,那么答案是否定的,不能保证哪个会进入...更多***.com/questions/4228864/…【参考方案4】:
锁将阻止其他线程执行锁块中包含的代码。线程必须等到锁块内的线程完成并释放锁。这确实会对多线程环境中的性能产生负面影响。如果您确实需要这样做,您应该确保锁定块中的代码可以非常快速地处理。您应该尽量避免昂贵的活动,例如访问数据库等。
【讨论】:
【参考方案5】:性能影响取决于您锁定的方式。你可以在这里找到一个很好的优化列表:http://www.thinkingparallel.com/2007/07/31/10-ways-to-reduce-lock-contention-in-threaded-programs/
基本上你应该尽量少加锁,因为它会让你等待的代码进入休眠状态。如果您在锁定中有一些繁重的计算或持久的代码(例如文件上传),则会导致巨大的性能损失。
【讨论】:
但是尝试编写低锁定代码通常会导致微妙的、难以发现和修复的错误,即使您是该领域的专家。使用锁通常是两害相权取其轻。您应该根据需要锁定多少,不多也不少! @LukeH:在某些使用模式中,低锁代码可以非常简单易用 [do oldValue = thing; newValue = updated(oldValue); while (CompareExchange(ref thing, newValue, oldValue) != oldValue
]。最大的危险是,如果需求超出了此类技术的处理范围,则可能很难调整代码来处理它。
链接已损坏。【参考方案6】:
锁语句中的部分只能由一个线程执行,因此所有其他线程将无限期地等待持有锁的线程完成。这可能会导致所谓的死锁。
【讨论】:
【参考方案7】:lock
语句被转换为对Monitor
的Enter
和Exit
方法的调用。
lock
语句将无限期地等待锁定对象被释放。
【讨论】:
【参考方案8】:lock 实际上隐藏了Monitor 类。
【讨论】:
【参考方案9】:根据Microsoft's MSDN,锁相当于:
object __lockObj = x;
bool __lockWasTaken = false;
try
System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken);
// Your code...
finally
if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj);
如果需要在运行时创建锁,可以使用开源的DynaLock。您可以在运行时创建新锁,并使用上下文概念指定锁的边界。
DynaLock 是开源的,源代码可在GitHub 获得
【讨论】:
以上是关于lock 语句在幕后做了啥?的主要内容,如果未能解决你的问题,请参考以下文章
MSCK REPAIR TABLE 在幕后做了啥,为啥这么慢?
GCC 对 int *a = 1,2,3,4,5 的语句做了啥?