Кросс-процессная синхронизация чтения-записи в.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.

Другие вопросы по тегам