簡易高效的Delphi原子隊列


本文提供Delphi一個基於原子操作的無鎖隊列,簡易高效。適用於多線程大吞吐量操作的隊列。

可用於Android系統和32,64位Windows系統。

 

感謝殲10和qsl提供了修改建議!

有如下問題:

1.必須事先足夠大開辟內存,大到不會出現隊列溢出了。

2.隊列大小必須是2的冪

3.不能壓入空指針

4.本程序還未經過工程應用考驗

unit Iocp.AtomQueue;

interface

Uses
  SysUtils,
  SyncObjs;

Type
  TAtomFIFO = Class
  Protected
    FWritePtr: Integer;
    FReadPtr: Integer;
    FCount:Integer;
    FHighBound:Integer;
    FisEmpty:Integer;
    FData: array of Pointer;
    function GetSize:Integer;
  Public
    procedure Push(Item: Pointer);
    function Pop: Pointer;
    Constructor Create(Size: Integer); Virtual;
    Destructor Destroy; Override;
    Procedure Empty;
    property Size: Integer read GetSize;
    property UsedCount:Integer read FCount;
  End;

Implementation

//創建隊列,大小必須是2的冪,需要開辟足夠大的隊列,防止隊列溢出

Constructor TAtomFIFO.Create(Size: Integer);
var
  i:NativeInt;
  OK:Boolean;
Begin
  Inherited Create;
  OK:=(Size and (Size-1)=0);

  if not OK then raise Exception.Create('FIFO長度必須大於等於256並為2的冪');

  try
    SetLength(FData, Size);
    FHighBound:=Size-1;
  except
    Raise Exception.Create('FIFO申請內存失敗');
  end;
End;

Destructor TAtomFIFO.Destroy;
Begin
  SetLength(FData, 0);
  Inherited;
End;

procedure TAtomFIFO.Empty;
begin
  while (TInterlocked.Exchange(FReadPtr, 0)<>0) and
  (TInterlocked.Exchange(FWritePtr, 0)<>0) and
  (TInterlocked.Exchange(FCount, 0)<>0) do;
end;

function TAtomFIFO.GetSize: Integer;
begin
  Result:=FHighBound+1;
end;

procedure TAtomFIFO.Push(Item:Pointer);
var
  N:Integer;
begin
  if Item=nil then Exit;

  N:=TInterlocked.Increment(FWritePtr) and FHighBound;
  FData[N]:=Item;
  TInterlocked.Increment(FCount);
end;

Function TAtomFIFO.Pop:Pointer;
var
  N:Integer;
begin
  if TInterlocked.Decrement(FCount)<0 then
  begin
    TInterlocked.Increment(FCount);
    Result:=nil;
  end
  else
  begin
    N:=TInterlocked.Increment(FReadPtr) and FHighBound;
    //假設線程A調用了Push,並且正好是第1個push,
    //執行了N:=TInterlocked.Increment(FWritePtr) and FHighBound,
    //還沒執行FData[N]:=Item, 被切換到其他線程
    //此時假設線程B調用了Push,並且正好是第2個push,並且執行完畢,這樣出現FCount=1,第2個Item不為空,而第一個Item還是nil(線程A還沒執行賦值)
    //假設線程C執行Pop,由於Count>0(線程B的作用)所以可以執行到這里,但此時FData[N]=nil(線程A還沒執行賦值),
    //因此線程C要等待線程A完成FData[N]:=Item后,才能取走FData[N]
    //出現這種情況的概率應該比較小,基本上不會浪費太多CPU
    while FData[N]=nil do Sleep(1);
    Result:=FData[N];

    FData[N]:=nil;
  end;
end;

End.

性能測試:

采用天地弦提供的評估程序,進行了一些修改,分別對使用不同的臨界區的隊列進行對比結果如下:

其中Swith是因隊列讀空,進行線程上下文切換的次數

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM