【问题标题】:Separate locking on two objects in .NET在 .NET 中单独锁定两个对象
【发布时间】:2017-03-01 18:24:13
【问题描述】:

我有一个具有三种方法的 .NET 类:Get1()、Get2() 和 Disconnect()。 Get1 和 Get2 可以在两个线程中同时调用,但我需要在 Disconnect 和其他两种方法之间保留互斥锁。换句话说,我不希望它在执行任一“获取”的过程中断开连接。

我可以用两把锁来做到这一点,像这样:

private object lock1 = new object();
private object lock2 = new object();

public void Get1()
{
  lock(lock1)
  {
    // work
  }
}

public void Get2()
{
  lock(lock2)
  {
    // work
  }
}

public void Disconnect()
{
  lock(lock1)
  {
    lock(lock2)
    {
      // work
    }
  }
}

这是实现上述结果的最佳做法吗?我应该担心死锁吗?如果有两个以上的可并行化方法会怎样——拥有 n 个锁似乎不是一个好的解决方案。

【问题讨论】:

  • 在 Disconnect 运行时执行 Get1 或 Get2 是否可行,或者它们是否相互排斥?我希望它们是互斥的,但是从您的问题来看,您几乎不希望在获取期间启动断开连接,但可能会在断开连接期间启动获取。
  • 我想在这些之间强制执行互斥锁,所以如果在 Disconnect 运行时调用 Get1 或 Get2,调用应该阻塞直到 Disconnect 完成(反之亦然)。
  • 是的,使用多个锁会引入更多死锁机会。如果要使用多个锁,规则是您必须始终以相同的顺序获取锁以防止死锁。这需要你在编写锁定代码时保持警惕。此外,如果有多个人倾向于编写某种锁管理器,则可以确保始终以相同的顺序获取锁。

标签: .net multithreading locking mutex


【解决方案1】:

编辑
我正在考虑更多关于这个问题,我知道它适合一个模式,但不记得它是什么,然后我查看了 System.Threading 类并找到了它 - 这符合 Reader/Writer 模式并且有一个锁那 - ReaderWriterLockSlim

这是一个使用此锁的示例类,并具有可以传递给您的 get 和断开调用的方法(或者您可以将此锁添加到您的类并遵循示例中的模式)

public class GetAndDisconnectLocker : IDisposable
{
    private ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();

    public void ExecuteGet(Action action)
    {
        _lock.EnterReadLock();
        try
        {
            action();
        }
        finally
        {
            _lock.ExitReadLock();
        }
    }

    public T ExecuteGet<T>(Func<T> function)
    {
        _lock.EnterReadLock();
        try
        {
            return function();
        }
        finally
        {
            _lock.ExitReadLock();
        }
    }

    public void ExecuteDisconnect(Action action)
    {
        _lock.EnterWriteLock();
        try
        {
            action();
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (disposing && _lock != null)
        {
            _lock.Dispose();
            _lock = null;
        }
    }
}

原答案
在我的脑海中,我可能会用ManualResetEvents 处理这个问题。您可以为每个方法创建一个事件,并在Disconnect 方法中使用WaitAll 以确保在继续使用Disconnect 之前完成所有Get 方法。如果您希望 Get 方法运行时间相当短,您可能需要考虑ManualResetEventSlim,它对于短时间运行的进程应该更高效。它使用旋转而不是事件句柄。

ManualResetEvent get1 = new ManualResetEvent(false);
ManualResetEvent get2 = new ManualResetEvent(false);
ManualResetEvent disc = new ManualResetEvent(false);

public void Get1()
{
    disc.WaitOne();
    get1.Reset();
    try
    {
        // Do stuff
    }
    finally
    {
        get1.Set();
    }
}

public void Get2()
{
    disc.WaitOne();
    get2.Reset();
    try
    {
        // Do stuff
    }
    finally
    {
        get2.Set();
    }
}

public void Disconnect()
{
    disc.Reset();
    try
    {
        WaitHandle.WaitAll(new [] { get1, get2 });
        // Do Stuff
    }
    finally
    {
        disc.Set();
    }
}

【讨论】:

  • @tamarind - 我已经用我认为更好的解决方案更新了我的答案,它处理了添加更多 get 方法而不需要为所有人添加句柄的情况(你在问题中提到)其中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多