线程安全模式下的多访问阵列/列表?

时间:2015-11-26 12:37:19

标签: multithreading list delphi

已经有一段时间了,我正在努力确保这个类有线程。 不幸的是,我无法理解在哪里采取适当的行动。

问题:

保存过程需要很长时间,因此我想在专用线程中使用它。 我们的想法是,如果线程保存正在运行,则根节点中的每个更改都会增加保存的需求。如果存在请求,则保存线程结束,然后重新保存仍然将请求置零,直到没有请求为止。

但是在我添加节点之前,没有问题。但是,目前要移除节点(然后是所有子节点),如果正在运行线程保存,则会创建正确的异常。

有一种解决方案或其他方法可以在保存根节点的同时使其可编辑。 显然没有为每个保存请求复制整个节点?

谢谢大家。

基类:

interface

uses

  System.Classes, System.SysUtils;

type

  TNode<T> = class
  public type
    TNodeForEachProc = reference to procedure( const ANode: TNode<T>; var ABreak: Boolean );
    TNodeWriteProc = reference to procedure( const AWriter: TBinaryWriter; const AData: T );
    TNodeReadProc = reference to function( const AReader: TBinaryReader ): T;
    TNodeBufferedWriteProc = reference to procedure( const AWriter: TBufferedWriter; const AData: T );
    TNodeBufferedReadProc = reference to function( const AReader: TBufferedReader ): T;
    TNodeProgressProc = reference to procedure( const AIndex, ACount: Integer );
  private
    FCount: Integer;
    FCapacity: Integer;
    FNodes: TArray<TNode<T>>;
    {$IFDEF AUTOREFCOUNT}[Weak]{$ENDIF}FParent: TNode<T>;
    procedure Grow;
    procedure SetCapacity( const AValue: Integer );
    function Get( AIndex: Integer ): TNode<T>; inline;
    function GetIndex: Integer;
    function GetLevel: Integer;
    procedure Adopt( const ANode: TNode<T> );
    procedure Orphan;
    procedure SetParent( const AValue: TNode<T> );
  protected
    property Nodes:TArray<TNode<T>> read FNodes;
  public
    Data: T;
    constructor Create( const ACapacity: Integer = 0 ); overload;
    constructor Create( const ANode: TNode<T> ); overload;
    destructor Destroy; override;
    procedure Assign( const ANode: TNode<T> );
    function Add( const AData: T ): TNode<T>;
    procedure Clear; inline;
    function Count( const ARecursive: Boolean = False ): Integer; inline;
    function Empty: Boolean; inline;
    procedure Delete( const AIndex: Integer; const ACount: Integer = 1 );
    function ForEach( const AProc: TNodeForEachProc; const ARecursive: Boolean = True ): Boolean;
    property Index: Integer read GetIndex;
    property Node[ Index: Integer ]: TNode<T> read Get; default;
    property Level: Integer read GetLevel;
    property Parent: TNode<T> read FParent write SetParent;
    procedure SaveToStream( const AStream: TStream; const AWriterProc: TNodeWriteProc; const AProgressProc: TNodeProgressProc = nil );
    procedure LoadFromStream( const AStream: TStream; const AReaderProc: TNodeReadProc; const AProgressProc: TNodeProgressProc = nil );
    procedure SaveToFile( const AFileName: TFileName; const AWriterProc: TNodeBufferedWriteProc; const AProgressProc: TNodeProgressProc = nil );
    procedure LoadFromFile( const AFileName: TFileName; const AReaderProc: TNodeBufferedReadProc; const AProgressProc: TNodeProgressProc = nil );
  end;

implementation

{ TNode<T> }

constructor TNode<T>.Create( const ACapacity: Integer = 0 );
begin
  SetCapacity( ACapacity );
end;

constructor TNode<T>.Create( const ANode: TNode<T> );
begin
  Assign( ANode );
end;

destructor TNode<T>.Destroy;
begin
  Clear;
  Orphan;
  inherited;
end;

procedure TNode<T>.Assign( const ANode: TNode<T> );
var
  VIndex: Integer;
begin
  Clear;
  Data := ANode.Data;
  FCount := ANode.FCount;
  SetLength( FNodes, FCount );
  for VIndex := 0 to FCount - 1 do
  begin
    FNodes[ VIndex ] := TNode<T>.Create;
    FNodes[ VIndex ].Assign( ANode.FNodes[ VIndex ] );
  end;
end;

function TNode<T>.Add( const AData: T ): TNode<T>;
begin
  Result := TNode<T>.Create;
  Result.Data := AData;
  Adopt( Result );
end;

procedure TNode<T>.Clear;
begin
  Delete( 0, FCount );
  FNodes := nil;
  FCount := 0;
  FCapacity := 0;
end;

function TNode<T>.Count( const ARecursive: Boolean = False ): Integer;
var
  VCount: Integer;
begin
  if ARecursive then
  begin
    VCount := 0;
    ForEach(
      procedure( const Node: TNode<T>; var ABreak: Boolean )
      begin
        Inc( VCount );
      end
    );
    Result := VCount;
  end else
    Result := FCount;
end;

procedure TNode<T>.Delete( const AIndex: Integer; const ACount: Integer = 1 );
var
  VIndex: Integer;
begin
  for VIndex := AIndex to AIndex + ACount - 1 do
  begin
    FNodes[ VIndex ].FParent := nil;
    FNodes[ VIndex ].DisposeOf;
    Dec( FCount );
  end;
  System.Delete( FNodes, AIndex, ACount );
end;

function TNode<T>.Empty: Boolean;
begin
  Result := FCount = 0;
end;

function TNode<T>.ForEach( const AProc: TNodeForEachProc; const ARecursive: Boolean = True ): Boolean;
type
  TTypeNode = TNode<T>;
var
  VIndex: Integer;
  VTypeNode: TTypeNode;
  VBreak: Boolean;
begin
  Result := True;
  if Assigned( AProc ) then
    for VIndex := 0 to Count - 1 do
    begin
      VTypeNode := FNodes[ VIndex ];
      VBreak := False;
      AProc( VTypeNode, VBreak );
      if VBreak then
        Exit( False );
      if ARecursive then
        if not VTypeNode.ForEach( AProc ) then
          Exit( False );
    end;
end;

procedure TNode<T>.Grow;
var
  VDelta: Integer;
begin
  if FCapacity > 64 then
    VDelta := FCapacity div 4
  else
    if FCapacity > 8 then
      VDelta := 16
    else
      VDelta := 4;
  SetCapacity( FCapacity + VDelta );
end;

procedure TNode<T>.SetCapacity( const AValue: Integer );
begin
  if AValue > FCapacity then
  begin
    SetLength( FNodes, AValue );
    FCapacity := AValue;
  end;
end;

function TNode<T>.Get( AIndex: Integer ): TNode<T>;
begin
  Result := FNodes[ AIndex ];
end;

function TNode<T>.GetIndex: Integer;
var
  VIndex: Integer;
begin
  if FParent <> nil then
    for VIndex := 0 to FParent.Count - 1 do
      if FParent[ VIndex ] = Self then
        Exit( VIndex );
  Result := - 1;
end;

function TNode<T>.GetLevel: Integer;
begin
  if FParent = nil then
    Result := 0
  else
    Result := FParent.Level + 1;
end;

procedure TNode<T>.Adopt( const ANode: TNode<T> );
var
  VIndex: Integer;
begin
  ANode.FParent := Self;
  VIndex := FCount;
  if VIndex = FCapacity then
    Grow;
  FNodes[ VIndex ] := ANode;
  Inc( FCount );
end;

procedure TNode<T>.Orphan;
begin
  if FParent <> nil then
  begin
    System.Delete( FParent.FNodes, Index, 1 );
    Dec( FParent.FCount );
  end;
end;

procedure TNode<T>.SetParent( const AValue: TNode<T> );
begin
  if FParent <> AValue then
  begin
    Orphan;
    FParent := AValue;
    if FParent <> nil then
      FParent.Adopt( Self );
  end;
end;

procedure TNode<T>.SaveToStream( const AStream: TStream; const AWriterProc: TNodeWriteProc; const AProgressProc: TNodeProgressProc = nil );
var
  VCount: Integer;
  VIndex: Integer;
  VWriter: TBinaryWriter;
begin
  if Assigned( AWriterProc ) then
  begin
    VCount := Count( True );
    VIndex := 0;
    VWriter := TBinaryWriter.Create( AStream );
    try
      VWriter.Write( VCount );
      AWriterProc( VWriter, Data );
      VWriter.Write( Count );
      ForEach(
        procedure( const ANode: TNode<T>; var ABreak: Boolean )
        begin
          AWriterProc( VWriter, ANode.Data );
          VWriter.Write( ANode.Count );
          if Assigned( AProgressProc ) then
          begin
            AProgressProc( VIndex, VCount );
            Inc( VIndex );
          end;
        end
      );
    finally
      VWriter.DisposeOf;
    end;
  end;
end;

procedure TNode<T>.LoadFromStream( const AStream: TStream; const AReaderProc: TNodeReadProc; const AProgressProc: TNodeProgressProc = nil );
var
  VIndex: Integer;
  VCount: Integer;
  VReader: TBinaryReader;
  VCurrentNode: TNode<T>;
  VEntryNode: TNode<T>;
begin
  if Assigned( AReaderProc ) then
  begin
    Clear;
    VReader := TBinaryReader.Create( AStream );
    try
      VCount := VReader.ReadInteger;
      Data := AReaderProc( VReader );
      SetCapacity( VReader.ReadInteger );
      VCurrentNode := Self;
      for VIndex := 0 to VCount - 1 do
      begin
        VEntryNode := VCurrentNode.Add( AReaderProc( VReader ) );
        VEntryNode.SetCapacity( VReader.ReadInteger );
        if VEntryNode.FCapacity > 0 then
          VCurrentNode := VEntryNode
        else if VCurrentNode.FCount = VCurrentNode.FCapacity then
          if VCurrentNode.Parent <> nil then
          begin
            VCurrentNode := VCurrentNode.Parent;
            while ( VCurrentNode.FCount = VCurrentNode.FCapacity ) and ( VCurrentNode <> Self ) do
              if VCurrentNode.Parent = nil then
                VCurrentNode := Self
              else
                VCurrentNode := VCurrentNode.Parent;
          end else
            VCurrentNode := Self;
        if Assigned( AProgressProc ) then
          AProgressProc( VIndex, VCount );
      end;
    finally
      VReader.DisposeOf;
    end;
  end;
end;

procedure TNode<T>.SaveToFile( const AFileName: TFileName; const AWriterProc: TNodeBufferedWriteProc; const AProgressProc: TNodeProgressProc = nil );
var
  VCount: Integer;
  VIndex: Integer;
  VWriter: TBufferedWriter;
begin
  if Assigned( AWriterProc ) then
  begin
    VCount := Count( True );
    VIndex := 0;
    VWriter := TBufferedWriter.Create( AFileName );
    try
      VWriter.WriteInteger( VCount );
      AWriterProc( VWriter, Data );
      VWriter.WriteInteger( Count );
      if Assigned( AProgressProc ) then
      begin
        AProgressProc( VIndex, VCount );
        Inc( VIndex );
      end;
      ForEach(
        procedure( const ANode: TNode<T>; var ABreak: Boolean )
        begin
          AWriterProc( VWriter, ANode.Data );
          VWriter.WriteInteger( ANode.Count );
          if Assigned( AProgressProc ) then
          begin
            AProgressProc( VIndex, VCount );
            Inc( VIndex );
          end;
        end
      );
    finally
      VWriter.DisposeOf;
    end;
  end;
end;

procedure TNode<T>.LoadFromFile( const AFileName: TFileName; const AReaderProc: TNodeBufferedReadProc; const AProgressProc: TNodeProgressProc = nil );
var
  VIndex: Integer;
  VCount: Integer;
  VReader: TBufferedReader;
  VCurrentNode: TNode<T>;
  VEntryNode: TNode<T>;
begin
  if Assigned( AReaderProc ) then
  begin
    Clear;
    VReader := TBufferedReader.Create( AFileName );
    try
      VCount := VReader.ReadInteger;
      Data := AReaderProc( VReader );
      SetCapacity( VReader.ReadInteger );
      VCurrentNode := Self;
      for VIndex := 0 to VCount - 1 do
      begin
        VEntryNode := VCurrentNode.Add( AReaderProc( VReader ) );
        VEntryNode.SetCapacity( VReader.ReadInteger );
        if VEntryNode.FCapacity > 0 then
          VCurrentNode := VEntryNode
        else if VCurrentNode.FCount = VCurrentNode.FCapacity then
          if VCurrentNode.Parent <> nil then
          begin
            VCurrentNode := VCurrentNode.Parent;
            while ( VCurrentNode.FCount = VCurrentNode.FCapacity ) and ( VCurrentNode <> Self ) do
              if VCurrentNode.Parent = nil then
                VCurrentNode := Self
              else
                VCurrentNode := VCurrentNode.Parent;
          end else
            VCurrentNode := Self;
        if Assigned( AProgressProc ) then
          AProgressProc( VIndex, VCount );
      end;
    finally
      VReader.DisposeOf;
    end;
  end;
end;

end.

2 个答案:

答案 0 :(得分:0)

向树中添加“some”多线程的最简单方法是将TCriticalSection对象添加到其根目录。关键在于确保保存和编辑不会同时发生。因此,当保存线程正在执行时,您可以使用主线程中的其他对象(例如,GUI可以正常工作),您也可以访问此树,但是当您在保存未执行时尝试编辑此树时,主线程将等待所有保存程序完成。

我想指出,在保存线程执行时添加新节点是有潜在危险的,因为它需要更新节点列表。此时内存的重新分配可能会发生(在其他地方有更大的块来包含所有元素),此时保存线程访问列表将会大大失败!

有可能将关键部分放在树的每个节点上,这样我们的主线程只会在它修改此时正确保存的节点时才会等待,你可以使用TThreadList代替TArray来存储允许的节点列表你可以添加新的节点,而旧的节点仍在保存,但这样你就有了成千上万的可能性来拍摄你自己的腿,只有极其小心它才能正常工作

所以我认为,您应该从一个关键部分开始,看看它是否足够快以满足您的需求。

答案 1 :(得分:0)

最后,感谢您的建议,这是我能做的最好的事情。 不幸的是,速度慢,并且在保存步骤中使用了双倍内存。

手机上的时间被认为是x500,而节省步骤x1000:

使用1.000.000短字符串在Interl I7中测试:&#34;项目索引XXX.XXX&#34;

谢谢大家。

enter image description here

一些日志:

...

1719毫秒的ForEach(1000000)

在1250毫秒内删除(1000000)

在3078毫秒内保存文件(940150)

在1828毫秒内添加(1000000)

1094毫秒内的ForEach(1000000)

在1219毫秒内添加(1000000)

在1406毫秒内删除(970781)

ForEach(970781)in 1625 ms

在1703毫秒内添加(1000000)

在531毫秒内删除(1000000)

在3625毫秒内保存文件(1000000)

ForEach(1000000)in 813 ms

在1016毫秒内添加(1000000)

...

简单的课程:

  TThreadSafeNode<T> = class
  private
    FNode: TNode<T>;
    FCriticalSection: TCriticalSection;
  public
    constructor Create;
    destructor Destroy; override;
    function Lock: TNode<T>;
    procedure Unlock;
  end;

测试线程:

var
  VThreadSafeNode: TThreadSafeNode<String>;
begin

  Randomize;

  VThreadSafeNode := TThreadSafeNode<String>.Create;

  // ---------------------------------------------------------------------------

  TThread.CreateAnonymousThread(
    procedure
    var
      VIndex: Integer;
      VCount: Integer;
      VTickCount: Cardinal;
      VRoot: TNode<String>;
    begin
      while True do
      begin

        VTickCount := TThread.GetTickCount;

        VRoot := VThreadSafeNode.Lock;
        try

          VRoot.Clear;

          VRoot.Capacity := Nodes;
          for VIndex := 1 to Nodes do
          begin
            VRoot.Add( 'Item Index ' + VIndex.ToString );
          end;

          VCount := VRoot.Count( True );

        finally
          VThreadSafeNode.Unlock;
        end;

        TThread.Synchronize(
          nil,
          procedure
          begin
            Memo1.Lines.Add( 'Adding ( ' + VCount.ToString + ' ) in ' + ( TThread.GetTickCount - VTickCount ).ToString + ' ms' );
          end
        );
      end;
    end
  ).Start;

  TThread.CreateAnonymousThread(
    procedure
    var
      VIndex: Integer;
      VCount: Integer;
      VTickCount: Cardinal;
      VRoot: TNode<String>;
    begin
      while True do
      begin
        VTickCount := TThread.GetTickCount;

        VRoot := VThreadSafeNode.Lock;
        try

          VIndex := Random( VRoot.Count );
          VRoot.Delete( VIndex, Random( VRoot.Count ) - VIndex );

          VCount := VRoot.Count( True );

        finally
          VThreadSafeNode.Unlock;
        end;

        TThread.Synchronize(
          nil,
          procedure
          begin
            Memo1.Lines.Add( 'Deleting ( ' + VCount.ToString + ' ) in ' + ( TThread.GetTickCount - VTickCount ).ToString + ' ms' );
          end
        );

        TThread.Sleep( Random( 1000 ) );
      end;
    end
  ).Start;

  TThread.CreateAnonymousThread(
    procedure
    var
      VCount: Integer;
      VTickCount: Cardinal;
      VRoot: TNode<String>;
    begin
      while True do
      begin
        VTickCount := TThread.GetTickCount;

        VRoot := VThreadSafeNode.Lock;
        try

          VRoot.ForEach(
            procedure( const Item: TNode<String>; var ABreak: Boolean )
            begin
              Item.Value := Item.Value;
            end
          );

          VCount := VRoot.Count( True );

        finally
          VThreadSafeNode.Unlock;
        end;

        TThread.Synchronize(
          nil,
          procedure
          begin
            Memo1.Lines.Add( 'ForEach ( ' + VCount.ToString + ' ) in ' + ( TThread.GetTickCount - VTickCount ).ToString + ' ms' );
          end
        );

        TThread.Sleep( Random( 1000 ) );
      end;
    end
  ).Start;

  TThread.CreateAnonymousThread(
    procedure
    var
      VCount: Integer;
      VLastPercent: Integer;
      VTickCount: Cardinal;
      VRoot: TNode<String>;
    begin
      while True do
      begin
        VTickCount := TThread.GetTickCount;

        VLastPercent := 0;

        VRoot := TNode<String>.Create;
        try
          VRoot.Assign( VThreadSafeNode.Lock );
        finally
          VThreadSafeNode.Unlock;
        end;

        try

          VRoot.SaveToFile(
            TPath.Combine( TPath.GetSharedDocumentsPath, 'test.txt' ),
            procedure( const AWriter: TBufferedWriter; const AData: String )
            begin
              AWriter.WriteString( AData );
            end,
            procedure( const AIndex, ACount: Integer )
            var
              VPercent: Integer;
            begin
              VPercent := Trunc( ( ( AIndex + 1 ) * 100 ) / ACount );
              if VLastPercent <> VPercent then
              begin
                VLastPercent := VPercent;
                TThread.Synchronize(
                  nil,
                  procedure
                  begin
                    ProgressBar1.Value := VLastPercent;
                  end
                );
              end;
            end
          );

          VCount := VRoot.Count( True );

        finally
          VRoot.DisposeOf;
        end;

        TThread.Synchronize(
          nil,
          procedure
          begin
            Memo1.Lines.Add( 'Saving File ( ' + VCount.ToString + ' ) in ' + ( TThread.GetTickCount - VTickCount ).ToString + ' ms' );
          end
        );

        TThread.Sleep( Random( 1000 ) );
      end;
    end
  ).Start;

end;