是否有跨进程工作的读/写锁定机制(类似于互斥锁,但读/写而不是排他锁定)?我想允许并发读取访问,但独占写入访问。
6 回答
Windows 不包括跨进程读写器锁。可以使用 Semaphore 和 Mutex 的组合来构造它们(Mutex 由 writer 持有以进行独占访问,或者由 Reader 持有,然后使用 Semaphore 释放其他 reader——即 writer 将只等待 mutex 和 reader 中的任何一个) .
但是,如果预计争用较低(即没有线程长时间持有锁),那么互斥可能仍然更快:读写锁的额外复杂性超过了允许多个读者进入的任何好处。(一个读写器只有当有更多的读者并且锁被持有很长时间时,锁才会更快——但只有你的分析可以证实这一点。)
不。正如 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 在上面的文章中查找“此解决方案次优”的字样。
我根据 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.");
}
}
如果您想避免Writer 饥饿,那么您可以考虑另一种算法。我研究了一些算法,这些算法可以避免 Writer 问题的饥饿(例如在本文中)。解决方案建议伪代码之一如下:伪代码图像。
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 存储库,因此您可以使用它。它还包含 ReaderWriterSynchronizerTryEnterReadLock()
和TryEnterWriteLock()
方法:https ://github.com/SzilvasiPeter/Cross-process-ReaderWriterLock
System.Threading.Mutex 有一个互斥锁,可用于进程内通信。如果您想要它不支持的功能,可以通过互斥锁来实现。
你看过System.Threading.ReaderWriteLock
吗?这是MSDN链接。