Многие читатели, один писатель, - можно ли избежать блокировки?

Скажите, что у вас есть список строк в памяти и многопоточная система со многими читателями, но только с одним потоком записи.

В целом, возможно ли реализовать такой тип системы на С# без использования блокировки? Будет ли реализация делать какие-либо предположения о том, как взаимодействуют потоки (или устанавливать ограничения на то, что они могут делать, когда)?

Ответы

Ответ 1

Да. Хитрость заключается в том, чтобы убедиться, что список остается неизменным. Писатель будет делать снимок основной коллекции, изменять моментальный снимок, а затем публиковать снимок для переменной, содержащей ссылку на основную коллекцию. Следующий пример демонстрирует это.

public class Example
{
  // This is the immutable master collection.
  volatile List<string> collection = new List<string>();

  void Writer()
  {
    var copy = new List<string>(collection); // Snapshot the collection.
    copy.Add("hello world"); // Modify the snapshot.
    collection = copy; // Publish the snapshot.
  }

  void Reader()
  {
    List<string> local = collection; // Acquire a local reference for safe reading.
    if (local.Count > 0)
    {
      DoSomething(local[0]);
    }
  }
}

Существует несколько предостережений с таким подходом.

  • Это работает только потому, что есть один писатель.
  • Записываются операции O (n).
  • Различные читатели могут одновременно использовать другую версию списка.
  • Это довольно опасный трюк. Существуют очень конкретные причины, по которым используется volatile, почему локальная ссылка получена на стороне читателя и т.д. Если вы не понимаете этих причин, не используйте шаблон. Слишком много, что может пойти не так.
  • Понятие о том, что это поточно-безопасное, является семантическим. Нет, он не будет бросать исключения, взрывать или разрывать целое в пространстве-времени. Но есть и другие способы, с помощью которых эта модель может вызвать проблемы. Знайте, каковы ограничения. Это не чудодейственное средство для любой ситуации.

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

Я описываю больше шаблонов в моем ответе здесь, а также тот, который безопасен для нескольких авторов.

Ответ 2

Это довольно распространенный запрос для библиотеки потоков для выполнения - такой вид блокировки обычно называют "блокировкой чтения-записи" или некоторым вариантом этой темы. Мне никогда не приходилось использовать реализацию С# специально, но есть одно: http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx

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

(Хорошо, так что это по-прежнему технически "блокировка", но это не "конструкция блокировки С#", это более сложный объект, специально разработанный для цели, указанной в вопросе. Поэтому я думаю, зависит ли он от правильного ответа о семантике и о том, почему он задавал вопрос.)

Ответ 3

Чтобы избежать блокировок, вы можете рассмотреть параллельные коллекции Microsoft . Эти коллекции предоставляют поточный безопасный доступ к коллекциям объектов как в упорядоченных, так и в неупорядоченных формах. Они используют некоторые аккуратные трюки, чтобы избежать блокировки внутри в максимально возможном количестве случаев.

Ответ 5

Подход с односвязным списком может использоваться без блокировок при условии, что писатель только вставляет/удаляет либо по голове, либо по хвосту. В любом случае, если вы заранее создадите новый node, вам понадобится только одна атомная операция (head = newHead; или tail.next = newTail), чтобы сделать операцию видимой для читателей.

С точки зрения производительности, вставки и удаления - O (1), а вычисление длины - O (n).