Кросс-процессная синхронизация чтения-записи в.NET?
Существует ли механизм блокировки чтения / записи, который работает во всех процессах (аналогично Mutex, но вместо эксклюзивной блокировки чтения / записи)? Я хотел бы разрешить одновременный доступ на чтение, но эксклюзивный доступ на запись.
6 ответов
Нет. Как заметил Ричард выше, в.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. Ищите слова "Это решение неоптимально" в статье выше.
Windows не включает межпроцессную блокировку Reader-Writer. Комбинация семафоров и мьютексов может быть использована для их создания (мьютекс удерживается автором для эксклюзивного доступа или читателем, который затем использует семафор для освобождения других читателей, т. Е. Писатели будут ждать только мьютекса и читателей для любого из них),
Однако, если ожидается, что конкуренция будет низкой (т. Е. Ни один поток не будет долго удерживать блокировку), взаимное исключение все равно может быть быстрее: дополнительная сложность блокировки чтения-записи подавляет любые преимущества, связанные с включением нескольких читателей. (Читатель-писатель Блокировка будет быстрее, только если будет намного больше считывателей, и блокировки будут удерживаться в течение значительного времени, но только ваше профилирование может подтвердить это.)
Я создал этот класс на основе ответа Павла. Я еще не тестировал его всесторонне, но я создал простое приложение 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 с консольными приложениями, так что вы можете поиграть с ним. Он также содержит ReaderWriterSynchronizer с
TryEnterReadLock()
и
TryEnterWriteLock()
методы: https://github.com/SzilvasiPeter/Cross-process-ReaderWriterLock
System.Threading.Mutex имеет мьютекс, который можно использовать для внутрипроцессного взаимодействия. Если вам нужна функциональность, которую он не поддерживает, ее можно реализовать через мьютекс.
Вы смотрели на System.Threading.ReaderWriteLock
? Вот ссылка MSDN.