Я написал простой стек узлов без блокировки (Delp[hi XE4, Win7-64, 32-разрядное приложение), в котором я могу иметь несколько «стеков» и одновременно извлекать/проталкивать узлы между ними из разных потоков. Он работает в 99,999% случаев, но в конечном итоге дает сбой при стресс-тесте с использованием всех ядер ЦП.
Урезанный, он сводится к этому (не настоящий/скомпилированный код): Узлы:
type POBNode = ^TOBNode;
[volatile]TOBNode = record
[volatile]Next : POBNode;
Data : Int64;
end;
Упрощенный стек:
type TOBStack = class
private
[volatile]Head:POBNode;
function Pop:POBNode;
procedure Push(NewNode:POBNode);
end;
procedure TOBStack.Push(NewNode:POBNode);
var zTmp : POBNode;
begin;
repeat
zTmp:=InterlockedCompareExchangePointer(Pointer(Head),nil,nil);(memory fenced-read*)
NewNode.Next:=zTmp;
if InterlockedCompareExchangePointer(Head,NewNode,zTmp)=zTmp
then break (*success*)
else continue;
until false;
end;
function TOBStack.Pop:POBNode;
begin;
repeat
Result:=InterlockedCompareExchangePointer(Pointer(Head),nil,nil);(memory fenced-read*)
if Result=nil
the exit;
NewHead:=Result.Next;
if InterlockedCompareExchangePointer(Pointer(Head),NewHead,Result)=Result
then break (*Success*)
else continue;(*Fail, try again*)
until False;
end;
Я пробовал много вариантов этого, но не могу заставить его быть стабильным. Если я создам несколько потоков, у каждого из которых есть стек, и все они отправят/вытолкнут в/из глобального стека, в конечном итоге произойдет сбой, но не быстро. Только когда я напрягаю его в течение нескольких минут подряд из нескольких нитей, в тугих петлях.
У меня не может быть скрытых ошибок в моем коде, поэтому мне нужно больше советов, чем задать конкретный вопрос, чтобы это работало на 100% без ошибок, 24/7. Выглядит ли вышеприведенный код нормально для потокобезопасного стека без блокировок? Что еще я могу посмотреть? Это невозможно нормально отладить, так как ошибки возникают в разных местах, говоря мне, что где-то происходит повреждение указателя или ОЗУ. Я также получаю повторяющиеся записи, что означает, что узел, который был извлечен из одного стека, а затем возвращен в тот же стек, все еще находится поверх старого стека... Невозможно ли это в соответствии с моим алгоритмом? Это заставило меня поверить, что возможно нарушить методы Delphi/Windows InterlockedCompareExchange или есть какие-то скрытые знания, которые мне еще предстоит раскрыть. :) (я также пробовал TInterlocked)
Я сделал полный тестовый пример, который можно скопировать с ftp.true.co.za. Там я запускаю 8 потоков, выполняющих 400 000 push/pops каждый, и он обычно падает (безопасно из-за проверок/созданных исключений) после нескольких циклов этих тестов, иногда много-много тестовых циклов завершается до того, как один из них внезапно падает.
Любой совет будет принят во внимание.
С уважением Антон Е.
InterlockedXXX
работают. Проблема в том, что ваш код этого не делает. - person David Heffernan   schedule 20.01.2014[volatile]
? - person David Heffernan   schedule 20.01.2014New Delphi Compiler Attributes
. - person LU RD   schedule 20.01.2014[volatile]
. Это влияет только на компиляторы ARM? - person David Heffernan   schedule 20.01.2014[volatile]
не обязательно, да и операции чтения с ограничением памяти в приведенном выше коде не нужны из-за сильной модели памяти x86 и x64. - person David Heffernan   schedule 20.01.2014System.Pas
атрибут [volatile] используется в TMonitor, TObject и некоторых других классах. Защита переменных FRefCount. Это, вероятно, что-то, что привнесла ARC, но, похоже, затронуты все платформы. - person LU RD   schedule 20.01.2014