Я внимательно изучил ваш код, и мне он кажется правильным. Одна вещь, которая сразу бросилась в глаза, заключалась в том, что вы использовали установленный шаблон для выполнения операции low-lock. Я вижу, что вы используете version
как своего рода виртуальный замок. Четные числа освобождаются, а нечетные числа приобретаются. И поскольку вы используете монотонно увеличивающееся значение для виртуальной блокировки, вы также избегаете проблемы ABA. Однако наиболее важным является то, что вы продолжаете цикл при попытке чтения до тех пор, пока значение виртуальной блокировки не станет таким же до начала чтения по сравнению с после он завершается. В противном случае вы сочтете это неудачным чтением и повторите попытку. Так что да, основная логика выполнена хорошо.
Так что насчет размещения генераторов барьеров памяти? Что ж, все это тоже выглядит неплохо. Требуются все Thread.MemoryBarrier
вызовы. Если бы мне пришлось придираться, я бы сказал, что вам нужен еще один в методе Write
, чтобы он выглядел так.
public void Write(T value)
{
// locks are full barriers
lock (write)
{
++version; // ++version odd: write in progress
Thread.MemoryBarrier();
this.value = value;
Thread.MemoryBarrier();
++version; // ++version even: write complete
}
}
Добавленный здесь вызов гарантирует, что ++version
и this.value = value
не поменяются местами. Теперь спецификация ECMA технически допускает такой вид переупорядочения инструкций. Однако реализация интерфейса командной строки Microsoft и оборудования x86 уже имеет изменчивую семантику записи, поэтому в большинстве случаев в этом нет необходимости. Но, кто знает, возможно, это будет необходимо в среде выполнения Mono, ориентированной на процессор ARM.
Что касается Read
вещей, я не могу найти никаких недостатков. Фактически, размещение ваших звонков - это именно то место, где я бы их поместил. Некоторые люди могут задаться вопросом, почему он вам не нужен до первого чтения version
. Причина в том, что внешний цикл улавливает случай, когда первое чтение было кэшировано из-за Thread.MemoryBarrier
дальше вниз.
Итак, это подводит меня к обсуждению производительности. Неужели это быстрее, чем жесткая блокировка в методе Read
? Что ж, я провел довольно обширное тестирование вашего кода, чтобы ответить на этот вопрос. Ответ однозначный: да! Это немного быстрее, чем жесткая блокировка. Я тестировал, используя Guid
в качестве типа значения, потому что это 128 бит и поэтому он больше, чем собственный размер слова моей машины (64 бита). Я также использовал несколько разных вариантов количества писателей и читателей. Ваша техника низкого запирания постоянно и значительно превосходила технику жесткого запора. Я даже пробовал несколько вариантов с использованием Interlocked.CompareExchange
для безопасного чтения, и все они тоже были медленнее. Фактически, в некоторых ситуациях это было медленнее, чем взятие жесткого замка. Я должен быть честным. Меня это совсем не удивило.
Я также провел довольно серьезное тестирование на валидность. Я создал тесты, которые будут работать довольно долго и ни разу не увидел разорванного чтения. А затем в качестве контрольного теста я настроил метод Read
таким образом, чтобы я знал, что он будет неправильным, и снова запустил тест. На этот раз, как и ожидалось, случайным образом стали появляться разорванные чтения. Я переключил код обратно на тот, который у вас есть, и разорванные чтения исчезли; опять же, как и ожидалось. Казалось, это подтвердило то, что я уже ожидал. То есть ваш код выглядит правильно. У меня нет большого разнообразия среды выполнения и аппаратных сред для тестирования (и у меня нет времени), поэтому я не хочу давать ему 100% одобрение, но я действительно думаю, что могу дать вашей реализации два больших пальца вверх сейчас.
Наконец, с учетом всего сказанного, я бы все равно избегал запускать это в производство. Да, это может быть правильно, но следующий парень, который должен поддерживать код, вероятно, не поймет его. Кто-то может изменить код и сломать его, потому что не понимает последствий своих изменений. Согласитесь, этот код довольно хрупкий. Даже малейшее изменение могло сломать его.
person
Brian Gideon
schedule
30.10.2013