如何在两个地方获得锁,但在一个地方释放

本文关键字:一个 方释放 释放 两个 方获得 | 更新日期: 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(等)。


随着上下文更清晰(注释),似乎WaitPulse是这样做的:

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.EnterMonitor.Exit,因为进入和离开作用域。

当你显式调用Monitor.Enter时,你正在获得锁,此时你需要调用Monitor.Exit来释放锁。

这行不通。

的代码
lock(lockObj)
{
    // do stuff
}

被翻译成类似

的形式
Monitor.Enter(lockObj)
try
{
    // do stuff
}
finally
{
    Monitor.Exit(lockObj)
}

这意味着您的代码进入锁两次,但只释放一次。根据文档,只有当ExitEnter一样经常被调用时,线程才会真正释放锁,而在你的代码中并不是这样。Summary:你的代码在调用obtainReleaseLock时不会死锁,但是lockObj上的锁永远不会被线程释放。您需要显式调用Monitor.Exit(lockObj),因此对Monitor.Enter的调用与对Monitor.Exit的调用数量相匹配。