【问题标题】:Cross-process read-write synchronization primative in .NET?.NET 中的跨进程读写同步原语?
【发布时间】:2010-08-17 15:00:07
【问题描述】:

是否有跨进程工作的读/写锁定机制(类似于互斥锁,但读/写而不是排他锁定)?我想允许并发读取访问,但独占写入访问。

【问题讨论】:

  • 跨进程还是跨线程?
  • @Bipul - 没有,但有一篇文章建议使用 System.Threading.Mutex 推出我自己的解决方案,因为操作系统中没有内置跨线程读写器锁。

标签: .net synchronization locking readerwriterlock cross-process


【解决方案1】:

没有。正如 Richard 上面提到的,.NET 中没有这种开箱即用的机制。 这是使用互斥锁和信号量实现它的方法。

方法 #1 在http://www.joecheng.com/blog/entries/Writinganinter-processRea.html 中描述,引用:

// create or open global mutex
GlobalMutex mutex = new GlobalMutex("IdOfProtectedResource.Mutex");
// create or open global semaphore
int MoreThanMaxNumberOfReadersEver = 100;

GlobalSemaphore semaphore = new GlobalSemaphore("IdOfProtectedResource.Semaphore", MoreThanMaxNumberOfReadersEver);

public void AcquireReadLock()
{
  mutex.Acquire();
  semaphore.Acquire();
  mutex.Release();
}

public void ReleaseReadLock()
{
  semaphore.Release();
}

public void AcquireWriteLock()
{
  mutex.Acquire();
  for (int i = 0; i < MoreThanMaxNumberOfReadersEver; i++)
    semaphore.Acquire(); // drain out all readers-in-progress
  mutex.Release();
}

public void ReleaseWriteLock()
{
  for (int i = 0; i < MoreThanMaxNumberOfReadersEver; i++)
    semaphore.Release();
}

另一种选择是:

读取锁定 - 如上所述。写锁定如下(伪代码):

- Lock mutex
- Busy loop until the samaphore is not taken AT ALL:
-- wait, release.
-- Release returns value; 
-- if value N-1 then break loop.
-- yield (give up CPU cycle) by using Sleep(1) or alternative
- Do write
- Release mutex

必须注意,更有效的方法是可能的,如下所示:http://en.wikipedia.org/wiki/Readers-writers_problem#The_second_readers-writers_problem 在上面的文章中查找“此解决方案次优”的字样。

【讨论】:

    【解决方案2】:

    Windows 不包含跨进程读写器锁。可以使用 Semaphore 和 Mutex 的组合来构造一个(互斥锁由编写器持有以进行独占访问,或者由 Reader 持有,然后使用 Semaphore 释放其他读取器 - 即编写器将只等待互斥锁和读取器中的任何一个) .

    但是,如果预计争用较低(即没有线程长时间持有锁),那么互斥可能仍然更快:读写锁的额外复杂性压倒了允许多个读取器进入的任何好处。(A只有当有更多的读者并且锁被持有很长时间时,读写器锁才会更快——但只有你的分析可以证实这一点。)

    【讨论】:

      【解决方案3】:

      我根据 Pavel 的回答创建了这个课程。我还没有对它进行广泛的测试,但我已经创建了一个简单的 winforms 应用程序来测试它,到目前为止它运行良好。

      请注意,它使用信号量,因此不支持重入。

      public class CrossProcessReaderWriterLock
      {
          private readonly string _name;
          const int _maxReaders = 10;
      
          readonly Mutex     _mutex;
          readonly Semaphore _semaphore;
      
          public CrossProcessReaderWriterLock(string name)
          {
              _name = name;
              _mutex     = new Mutex(false, name + ".Mutex");
              _semaphore = new Semaphore(_maxReaders, _maxReaders, name + ".Semaphore");
          }
      
          public void AcquireReaderLock()
          {
              //Log.Info($"{_name} acquiring reader lock...");
      
              _mutex    .WaitOne();
              _semaphore.WaitOne();
              _mutex    .ReleaseMutex();
      
              //Log.Info($"{_name} reader lock acquired.");
          }
      
          public void ReleaseReaderLock()
          {
              _semaphore.Release();
      
              //Log.Info($"{_name} reader lock released.");
          }
      
          public void AcquireWriterLock()
          {
              //Log.Info($"{_name} acquiring writer lock...");
      
              _mutex.WaitOne();
      
              for (int i = 0; i < _maxReaders; i++)
                  _semaphore.WaitOne(); // drain out all readers-in-progress
      
              _mutex.ReleaseMutex();
      
              //Log.Info($"{_name} writer lock acquired.");
          }
      
          public void ReleaseWriterLock()
          {
              for (int i = 0; i < _maxReaders; i++)
                  _semaphore.Release();
      
              //Log.Info($"{_name} writer lock released.");
          }
      }
      

      【讨论】:

      • 谢谢。我用你的例子作为我的基础,它只依赖于信号量并且是异步/等待友好的。
      【解决方案4】:

      如果您想避免 Writer starvation,那么您可以考虑另一种算法。我研究了一些算法,这些算法可以避免 Writer 问题的饥饿(例如,在这个 paper 中)。解决方案提案伪代码之一如下:pseudo-code image

      public class ReadWriterSynchronizer : IDisposable
      {
          public ReadWriterSynchronizer(string name, int maxReaderCount)
          {
              myIncomingOperation = new Semaphore(1, 1, name + ".Incoming");
              myReadOperation = new Semaphore(1, 1, name + ".Reader");
              myWriteOperation = new Semaphore(1, 1, name + ".Writer");
              myCrossprocessCounter = new ReaderCounter(name + ".Counter", maxReaderCount);
          }
      
          public void EnterReadLock()
          {
              myIncomingOperation.WaitOne();
              myReadOperation.WaitOne();
      
              // Local variable is necessary, because of optimalization
              int currentCount = myCrossprocessCounter.Increase();
              if (currentCount == 1)
              {
                  myWriteOperation.WaitOne();
              }
      
              myReadOperation.Release();
              myIncomingOperation.Release();
          }
      
          public void ExitReadLock()
          {
              myReadOperation.WaitOne();
      
              // Local variable is necessary, because of optimalization
              int currentCount = myCrossprocessCounter.Decrease();
              if (currentCount == 0)
              {
                  myWriteOperation.Release();
              }
      
              myReadOperation.Release();
          }
      
          public void EnterWriteLock()
          {
              myIncomingOperation.WaitOne();
              myWriteOperation.WaitOne();
          }
      
          public void ExitWriteLock()
          {
              myWriteOperation.Release();
              myIncomingOperation.Release();
          }
      
          public void Dispose()
          {
              myIncomingOperation?.Dispose();
              myReadOperation?.Dispose();
              myWriteOperation?.Dispose();
              myCrossprocessCounter?.Dispose();
      
              GC.SuppressFinalize(this);
          }
      
          private readonly ReaderCounter myCrossprocessCounter;
          private readonly Semaphore myIncomingOperation;
          private readonly Semaphore myReadOperation;
          private readonly Semaphore myWriteOperation;
      }
      

      不幸的是,ctr 变量是一个整数,因此它只能在进程间场景中工作。我决定用 信号量计数器 (ReaderCounter) 替换整数计数器,这样它就可以用于跨进程通信。本质上,我使用WaitOne(0)减少Release()增加 读者计数器。

      internal class ReaderCounter : IDisposable
      {
          internal ReaderCounter(string name, int maxConcurrentRead)
          {
              MaximumCount = maxConcurrentRead + InitialCount;
              myReadCounterSemaphore = new Semaphore(InitialCount, MaximumCount, name);
              myIncomingOperation = new Semaphore(1, 1, name + ".Incoming");
          }
      
          internal int Increase()
          {
              int counter = RetrieveCurrentCount();
      
              // Not allowing to exceed maximum count
              if (counter != MaximumCount - 1)
              {
                  counter = myReadCounterSemaphore.Release();
              }
              else
              {
                  counter++;
              }
      
              return counter;
          }
      
          internal int Decrease()
          {
              int counter = RetrieveCurrentCount() - 1;
              myReadCounterSemaphore.WaitOne(0);
      
              return counter;
          }
      
          public void Dispose()
          {
              myReadCounterSemaphore?.Dispose();
              myIncomingOperation?.Dispose();
      
              GC.SuppressFinalize(this);
          }
      
          internal int MaximumCount { get; private set; }
      
          private const int InitialCount = 1;
          private readonly Semaphore myReadCounterSemaphore;
          private readonly Semaphore myIncomingOperation;
      
          private int RetrieveCurrentCount()
          {
              myReadCounterSemaphore.WaitOne(0);
              int counter = myReadCounterSemaphore.Release();
              return counter;
          }
      }
      

      注意:为方便使用,在阅读器计数器中添加了 1 个河豚计数。例如,使用 5 个阅读器意味着 [1,6] 初始信号量计数。从最小计数减少返回-1,从最大计数增加返回最大计数+1。

      更新:我创建了一个带有控制台应用程序的 GitHub 存储库,因此您可以使用它。它还包含带有TryEnterReadLock()TryEnterWriteLock() 方法的ReaderWriterSynchronizer:https://github.com/SzilvasiPeter/Cross-process-ReaderWriterLock

      【讨论】:

        【解决方案5】:

        System.Threading.Mutex 有一个可用于进程内通信的互斥锁。如果您想要它不支持的功能,可以通过互斥体来实现。

        【讨论】:

        • 你知道任何实现读/写互斥锁的示例吗?
        • 这家伙说其实不可能:stackoverflow.com/questions/1008726/…
        • @PavelRadzivilovsky 这个问题说如果不使用至少一个内核级对象(例如互斥体)就无法完成。
        • @PavelRadzivilovsky 实际上,答案明确指出“但是,您可以做的是使用自旋锁并在出现争用时回退到互斥锁。”
        【解决方案6】:

        你看过System.Threading.ReaderWriteLock吗?这是MSDN 链接。

        【讨论】:

        • 啊。误解了这个问题(仍然是第一杯咖啡)。我将保留几分钟,然后将其删除。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-11-20
        • 1970-01-01
        • 2011-09-16
        • 1970-01-01
        • 2012-01-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多