Я отдам свои два цента, не будучи на 100% уверенным, что то, что я собираюсь сказать, правильно. Джо Даффи - эксперт мирового уровня в многопоточности, но я думаю, что в этой реализации он был слишком осторожен в отношении перекрестная видимость внутреннего состояния класса LockFreeStack<T>
. На мой взгляд, волатильность поля Node.m_next
избыточна. Экземпляры Node
являются изменяемыми, но они изменяются только перед, когда они связаны во внутреннем связанном списке. После этой фазы они практически неизменны. Эта изменяемая фаза выполняется одним потоком, поэтому нет никаких шансов, что другой поток увидит устаревшую версию экземпляра Node
.
Это оставляет только возможность переупорядочения инструкций в качестве причины для объявления Node.m_next
как volatile
. Поскольку присвоение n.m_next = h;
зажато между чтением другого изменчивого поля (m_head
) и операцией Interlocked.CompareExchange
, я бы предположил, что возможное изменение порядка инструкций, которое может поставить под угрозу правильность реализации, уже предотвращено. , но, как я уже сказал, я не уверен на 100%.
Тем не менее, я почти уверен, что реализация класса LockFreeStack<T>
может быть улучшена с точки зрения производительности за счет того, что станет немного более выделяемым, сделав неизменяемым класс Node
. В настоящее время (C # 9) этого можно достичь, просто переключившись с типа class
на тип _ 13_. Вот как могла бы выглядеть такая реализация:
class LockFreeStack<T>
{
record Node(T Value, Node Next) { }
Node _head;
void Push(T value)
{
Node h = Volatile.Read(ref _head);
while (true)
{
Node n = new Node(value, h);
var original = Interlocked.CompareExchange(ref _head, n, h);
if (original == h) break;
h = original;
}
}
T Pop()
{
Node h = Volatile.Read(ref _head);
while (true)
{
if (h == null) throw new Exception("Stack empty");
var original = Interlocked.CompareExchange(ref _head, h.Next, h);
if (original == h) break;
h = original;
}
return h.Value;
}
}
Обратите внимание, что стоимость волатильности возникает только один раз для каждой Push
или Pop
операции. Можно даже утверждать, что Volatile.Read
из поле _head
можно было бы полностью опустить, поскольку возможное устаревшее значение _head
в любом случае будет исправлено первым _ 20_, затратив лишь дополнительную итерацию while (true)
цикла.
person
Theodor Zoulias
schedule
05.06.2021
m_head
также помечено какvolatile
. Вам тоже нужно объяснение, или это то, что вы понимаете? - person Theodor Zoulias   schedule 05.06.2021