ReaderWriterLockSlim Блокировка чтения до завершения всех записей в очереди - PullRequest
5 голосов
/ 23 ноября 2011

Я пытаюсь использовать класс ReaderWriterLockSlim для управления списком.

В этом списке много чтений и мало записей, мои чтения быстрые, а записи медленные.

У меня есть простой тестовый жгут, написанный для проверки работы замка.

Если возникает следующая ситуация

Thread 1 - Start Write
Thread 2 - Start Read
Thread 3 - Start Write

Тогда результат будет следующим:

Thread 1 starts its write and locks the list.
Thread 2 adds itself to the read queue.
Thread 3 adds itself to the write queue.
Thread 1 finishes writing and releases the lock
Thread 3 aquires the lock and starts its write
Thread 3 finishes writing and releases the lock
Thread 2 performs its read

Есть ли способ изменить поведение блокировки, чтобы любые запросы на чтение, находящиеся в очереди до блокировки записи, могли быть выполнены до того, как будут предоставлены блокировки записи?

РЕДАКТИРОВАТЬ: код, который демонстрирует проблему Iиметь ниже

public partial class SimpleLock : System.Web.UI.Page
{
    public static ReaderWriterLockSlim threadLock = new ReaderWriterLockSlim();

    protected void Page_Load(object sender, EventArgs e)
    {
        List<String> outputList = new List<String>();

        Thread thread1 = new Thread(
            delegate(object output)
            {
                ((List<String>)output).Add("Write 1 Enter");
                threadLock.EnterWriteLock();
                ((List<String>)output).Add("Write 1 Begin");
                Thread.Sleep(100);
                ((List<String>)output).Add("Write 1 End");
                threadLock.ExitWriteLock();
                ((List<String>)output).Add("Write 1 Exit");
            }
        );
        thread1.Start(outputList);

        Thread.Sleep(10);

        Thread thread2 = new Thread(
            delegate(object output)
            {
                ((List<String>)output).Add("Read 2 Enter");
                threadLock.EnterReadLock();
                ((List<String>)output).Add("Read 2 Begin");
                Thread.Sleep(100);
                ((List<String>)output).Add("Read 2 End");
                threadLock.ExitReadLock();
                ((List<String>)output).Add("Read 2 Exit");
            }
        );
        thread2.Start(outputList);

        Thread.Sleep(10);

        Thread thread3 = new Thread(
            delegate(object output)
            {
                ((List<String>)output).Add("Write 3 Enter");
                threadLock.EnterWriteLock();
                ((List<String>)output).Add("Write 3 Begin");
                Thread.Sleep(100);
                ((List<String>)output).Add("Write 3 End");
                threadLock.ExitWriteLock();
                ((List<String>)output).Add("Write 3 Exit");
            }
        );
        thread3.Start(outputList);

        thread1.Join();
        thread2.Join();
        thread3.Join();

        Response.Write(String.Join("<br />", outputList.ToArray()));
    }
}

1 Ответ

3 голосов
/ 23 ноября 2011

Есть ли способ изменить поведение блокировки так, чтобы любой Запросы на чтение, которые были поставлены в очередь до блокировки записи, могут завершить до предоставления блокировки записи?

А как насчет того, чтобы почти полностью отказаться от использования замков? Во время записи вы можете получить блокировку, скопировать исходную структуру данных, изменить копию, а затем опубликовать новую структуру данных, заменив старую ссылку новой ссылкой. Поскольку вы никогда не изменяете структуру данных после того, как она была «опубликована», вам вообще не нужно будет блокировать чтение.

Вот как это работает:

public class Example
{
  private object writelock = new object();
  private volatile List<string> data = new List<string>();

  public void Write(string item)
  {
    lock (writelock)
    {
      var copy = new List<string>(data); // Create the copy.
      copy.Add(item); // Modify the data structure.
      data = copy; // Publish the modified data structure.
    }
  }

  public string Read(int index)
  {
    return data[index];
  }
}

Уловка, которую мы здесь используем, заключается в неизменности того, на что ссылается переменная data. Единственное, что нам нужно сделать, это пометить переменную как volatile.

Обратите внимание, что этот прием работает только в том случае, если записи выполняются достаточно редко, а структура данных достаточно мала, чтобы операция копирования была дешевой. Это не be all end all решение. Он не идеален для каждого сценария, но может работать только для вас.

...