如何在两个地方获得锁,但在一个地方释放
本文关键字:一个 方释放 释放 两个 方获得 | 更新日期: 2023-09-27 18:10:04
我是c#新手。我需要在2个方法中获得锁,但在一个方法中释放。这能行吗?
public void obtainLock() {
Monitor.Enter(lockObj);
}
public void obtainReleaseLock() {
lock (lockObj) {
doStuff
}
}
尤其可以先叫obtainLock
,再叫obtainReleaseLock
吗?c#中允许"doubelock"吗?这两个方法总是在同一个线程中调用,但是lockObj
在另一个线程中用于同步。
upd:在所有注释之后,您如何看待这样的代码?它是理想的吗?
public void obtainLock() {
if (needCallMonitorExit == false) {
Monitor.Enter(lockObj);
needCallMonitorExit = true;
}
// doStuff
}
public void obtainReleaseLock() {
try {
lock (lockObj) {
// doAnotherStuff
}
} finally {
if (needCallMonitorExit == true) {
needCallMonitorExit = false;
Monitor.Exit(lockObj);
}
}
}
是的,锁是"可重入的",因此调用可以"双锁"(您的短语)lockObj
。但请注意,它需要释放的次数与服用的次数相同;你需要确保有一个对应的"ReleaseLock"来匹配"ObtainLock"。
但是,我建议让调用方lock(...)
处理您公开的某些属性更容易:
public object SyncLock { get { return lockObj; } }
现在调用者可以(而不是obtainLock()
):
lock(something.SyncLock) {
//...
}
更容易写对。因为这与内部使用的底层lockObj
是相同的,所以这对任何一种用法都是同步的,即使在锁定SyncLock
的代码中使用了obtainReleaseLock
(等)。
随着上下文更清晰(注释),似乎Wait
和Pulse
是这样做的:
void SomeMethodThatMightNeedToWait() {
lock(lockObj) {
if(needSomethingSpecialToHappen) {
Monitor.Wait(lockObj);
// ^^^ this ***releases*** the lock (however many times needed), and
// enters the pending-queue; when *another* thread "pulses", it
// enters the ready-queue; when the lock is *available*, it
// reacquires the lock (back to as many times as it held it
// previously) and resumes work
}
// do some work, happy that something special happened, and
// we have the lock
}
}
void SomeMethodThatMightSignalSomethingSpecial() {
lock(lockObj) {
// do stuff
Monitor.PulseAll(lockObj);
// ^^^ this moves **all** items from the pending-queue to the ready-queue
// note there is also Pulse(...) which moves a *single* item
}
}
请注意,当使用Wait
时,您可能希望使用接受超时的过载,以避免永远等待;还要注意,必须循环并重新验证的情况很常见,例如:
lock(lockObj) {
while(needSomethingSpecialToHappen) {
Monitor.Wait(lockObj);
// at this point, we know we were pulsed, but maybe another waiting
// thread beat us to it! re-check the condition, and continue; this might
// also be a good place to check for some "abort" condition (and
// remember to do a PulseAll() when aborting)
}
// do some work, happy that something special happened, and we have the lock
}
您必须使用Monitor来实现此功能。请注意,如果您不小心使用锁,并且在代码的不同区域获取和释放它们可能会有风险,那么您将自己置于死锁和竞争条件之下。
Monitor.Exit(lockObj);
在给定的时间内,只有一个所有者可以持有锁;它是独家的。虽然锁可以链接,但更重要的是确保获得和释放适当的次数,避免难以诊断的线程问题。
当你通过lock { ... }
包装你的代码时,你实际上是在调用Monitor.Enter
和Monitor.Exit
,因为进入和离开作用域。
当你显式调用Monitor.Enter
时,你正在获得锁,此时你需要调用Monitor.Exit
来释放锁。
这行不通。
的代码lock(lockObj)
{
// do stuff
}
被翻译成类似
的形式Monitor.Enter(lockObj)
try
{
// do stuff
}
finally
{
Monitor.Exit(lockObj)
}
这意味着您的代码进入锁两次,但只释放一次。根据文档,只有当Exit
和Enter
一样经常被调用时,线程才会真正释放锁,而在你的代码中并不是这样。Summary:你的代码在调用obtainReleaseLock
时不会死锁,但是lockObj
上的锁永远不会被线程释放。您需要显式调用Monitor.Exit(lockObj)
,因此对Monitor.Enter
的调用与对Monitor.Exit
的调用数量相匹配。