Существует ли глобальная блокировка чтения/записи?

У меня есть несколько веб-приложений asp.net, обслуживающих набор файлов. Периодически каждый обновляет файл перед его обслуживанием, но он не может обновить файл, если он используется.

Я мог бы решить эту проблему, используя именованный mutex, где имя - путь к файлу (конечно, заменив недействительные символы). Я использовал это в других ситуациях, но вы можете видеть, насколько он неэффективен. Только один процесс сможет обслуживать файл за раз.

Блокировка считывателя/записи была бы идеальной, но они предназначены для работы в рамках одного процесса. Плюс я должен был бы создать блокировку чтения/записи для каждого файла, который может быть обновлен, и есть много.

Мне действительно нужна блокировка чтения/записи, которую можно назвать мьютексом. Что-то подобное существует? Или можно создать такую ​​вещь, используя существующие блокировки?

Ответы

Ответ 1

Можно моделировать блокировку чтения/записи с использованием Mutex и семафора. Я бы этого не сделал, если бы мне приходилось обращаться к нему тысячи раз в секунду, но для десятков или, возможно, для сотен раз в секунду это должно работать нормально.

Эта блокировка разрешает эксклюзивный доступ к 1 записи или параллельному доступу с помощью N (возможно, большого, но вы должны определить его).

Вот как это работает. В качестве примера я приведу 10 читателей.

Инициализировать именованный Mutex, первоначально неизмеримый, и названный Семафор с 10 слотами:

  Mutex m = new Mutex(false, "MyMutex");
  Semaphore s = new Semaphore(10, 10, "MySemaphore");

Приобретать блокировку считывателя:

// Lock access to the semaphore.
m.WaitOne();
// Wait for a semaphore slot.
s.WaitOne();
// Release mutex so others can access the semaphore.
m.ReleaseMutex();

Блокировка считывателя релизов:

s.Release();

Приобретать блокировку записи:

// Lock access to the seamphore
m.WaitOne();
// Here we're waiting for the semaphore to get full,
// meaning that there aren't any more readers accessing.
// The only way to get the count is to call Release.
// So we wait, then immediately release.
// Release returns the previous count.
// Since we know that access to the semaphore is locked
// (i.e. nobody can get a slot), we know that when count
// goes to 9 (one less than the total possible), all the readers
// are done.
s.WaitOne();
int count = s.Release();
while (count != 9)
{
    // sleep briefly so other processes get a chance.
    // You might want to tweak this value.  Sleep(1) might be okay.
    Thread.Sleep(10);
    s.WaitOne();
    count = s.Release();
}

// At this point, there are no more readers.

Блокировка фиксатора записи:

m.ReleaseMutex();

Несмотря на то, что хрупкий (для каждого процесса с использованием этого лучше иметь одинаковое число для подсчета семафора!), я думаю, что он будет делать то, что вы хотите, пока вы не пытаетесь слишком сильно ударить его.

Ответ 2

Немного пораньше, но я недавно написал названный блок чтения/записи - http://unintelligible.org/blog/2009/10/20/named-reader-writer-lock-in-c/. Это работает, поддерживая карту имени/блокировки ReaderWriterLockSlims и окружающий доступ к этой карте в Мониторе.

Ответ 3

Я не думаю, что есть что-то, что соответствует вашим потребностям, как указано (хотя я оставляю за собой право ошибаться).

Вы можете обслуживать файл с помощью службы. Это решает две проблемы. Во-первых, как вы сказали, проблема concurrency. Кроме того, даже если вы можете добиться синхронизации, становится все труднее и уродливо, если вы начнете балансировку нагрузки. Обслуживание файла с помощью службы, скорее всего, будет стоить вам производительности, но решает проблему синхронизации.

Ответ 4

Как насчет этого? Не обслуживайте файлы. Подайте копии файлов. Когда вам нужно внести изменения, создайте новый файл и затем откройте копию этого файла.

Ответ 5

Я ценю отличный ответ Джима Мишеля, но я вижу возможность улучшения производительности, избегая Thread.Sleep() и избегая конфликтов блокировок, когда несколько читателей пытаются приобрести на то же время!

Инициализация

  Mutex writer = new Mutex(false, "Global\\MyWriterMutex");
  Semaphore readers = new Semaphore(int.MaxValue, int.MaxValue, "Global\\MyReadersSemaphore");
  EventWaitHandle readAllowed = new EventWaitHandle(true, EventResetMode.ManualReset, "Global\\MyReadAllowedEvent");
  EventWaitHandle readFinished = new EventWaitHandle(false, EventResetMode.ManualReset, "Global\\MyReadFinishedEvent");

Читатель

  while (true)
  {
    // signal that I'm reading 
    readers.WaitOne();

    // check whether I'm actually allowed to read
    if (readAllowed.WaitOne(0))
    {
      break; // great!
    }

    // oops, nevermind, signal that I'm not reading
    readers.Release();
    readFinished.Set();

    // block until it ok to read
    readAllowed.WaitOne();
  }

  try
  {
    readData();
  }
  finally
  {
    // signal that I'm no longer reading
    readers.Release();
    readFinished.Set();
  }

Writer

  // block until I am the only writer
  try
  {
    writer.WaitOne();
  }
  catch (AbandonedMutexException)
  {
    // The mutex was abandoned in another process, but it was still acquired
  }

  // signal that readers need to cease
  readAllowed.Reset();

  // loop until there are no readers
  int readerCount = -1;
  while (readerCount != 0)
  {
    // wipe the knowledge that a reader recently finished
    readFinished.Reset();

    // check if there is a reader
    readers.WaitOne();
    readerCount = int.MaxValue - (readers.Release() + 1);
    if (readerCount > 0)
    {
      // block until some reader finishes
      readFinished.WaitOne();
    }
  }

  try
  {
    writeData();
  }
  finally
  {
    // signal that readers may continue, and I am no longer the writer
    readAllowed.Set();
    writer.ReleaseMutex();
  }