Delphi [volatile]和InterlockedCompareExchange不可靠?

时间:2014-01-20 10:57:04

标签: multithreading delphi volatile lock-free interlocked

我编写了一个简单的无锁节点堆栈(Delp [hi XE4,Win7-64,32位应用程序],我可以在其中同时从各个线程中拥有多个“堆栈”和pop / push节点。 它在99.999%的时间内工作,但最终在使用所有CPU内核的压力测试下出现故障。

剥离,它归结为此(不是真实/编译代码): 节点是:

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。 对于无锁的线程安全堆栈,上面的代码是否正常? 我还能看到什么?这是不可能正常调试的,因为错误发生在各个地方,告诉我在某处发生指针或RAM损坏。我也得到重复的条目,这意味着一个节点弹出一个堆栈,然后又返回到同一个堆栈,仍然在旧堆栈的顶部...根据我的算法不可能发生?这让我相信它可能会违反Delphi / Windows InterlockedCompareExchange方法,或者我还有一些隐藏的知识尚未透露。 :)(我也尝试过TInterlocked)

我已经制作了一个完整的测试用例,可以从ftp.true.co.za复制。 在那里,我运行了8个线程,每个线程执行400,000个推送/弹出,并且在经过几个周期的测试后,它通常会崩溃(安全地由于检查/引发的异常),有时在一个突然崩溃之前完成许多测试周期。

任何建议都将受到赞赏。

此致 安东 ë

1 个答案:

答案 0 :(得分:3)

起初我对这是一个ABA problem as indicated by gabr持怀疑态度。在我看来:如果一个线程查看当前Head,另一个线程推送然后弹出;你很高兴仍以相同的方式在同一Head上运作。

但是,请从Pop方法中考虑这一点:

NewHead:=Result.Next;
if InterlockedCompareExchangePointer(Pointer(Head),NewHead,Result)=Result
  • 如果在第一行之后换出线程。
  • NewHead的值存储在本地变量中。
  • 然后另一个线程成功弹出该线程所针对的节点。
  • 它还设法将相同的节点推回,但在第一个线程恢复之前,Next的值不同。
  • 第二行将通过比较检查,允许head从本地变量接收NewHead值。
  • 但是,NewHead的当前值不正确,从而破坏了您的堆栈。

这个问题有一个微妙的变化甚至没有你的测试应用程序覆盖。在测试应用中未检查此问题,因为在测试结束之前,您不会销毁任何节点。

  • 看看现在的头脑
  • 另一个线程弹出一些节点。
  • 节点被销毁,新节点被创建并推送。
  • 当你的“寻找线程”再次处于活动状态时,它可能会看到一个完全不同的节点,它恰巧位于同一地址。
  • 如果您正在弹出,可以将垃圾指针分配给Head

除以上形式外... ... 看看你的测试应用程序,还有一些非常狡猾的代码。 E.g。

您生成“随机数”:J:=GetTickCount and 7;(*Get a 'random' number 0..7*)

  • 您是否意识到计算机有多快?
  • 您是否意识到GetTickCount会在紧密循环中产生大量重复项?
  • 即。你生成的数字就像 random。
  • 当评论与代码不一致时,我的蜘蛛般的感觉开始了。

您正在分配硬编码大小的内存:GetMem(zTmp,12);(*Allocate new node*)

  • 你为什么不使用SizeOf
  • 您正在使用多平台编译器....该结构的大小可能会有所不同。
  • 绝对没有理由对结构的大小进行硬编码。

现在,鉴于这两个例子,我不完全相信你的测试代码中也没有错误。