Search code examples
multithreadingdelphiparallel-processingdelphi-10.1-berlin

TParallel.For: Store values in a TList while they are calculated in a TParallel.For loop


I want to use a TParallel.&For loop to calculate, for example, the prime numbers between 1 and 100000 and save all these prime numbers in AList: TList<Integer>:

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
  AList: TList<Integer>;
  LoopResult: Tparallel.TLoopResult;
begin
  AList:=TList<Integer>.Create;
  TParallel.&For(1, 100000,
    procedure(AIndex: Integer)
    begin
      if IsPrime(AIndex) then
      begin
        //add the prime number to AList
      end;
    end);

  //show the list
  for i := 0 to AList.Count-1 do
  begin
    Memo1.Lines.Add(IntToStr(AList[i]));
  end;

end;

The calculations can be performed in parallel without issue but the TList is a shared resource. How can I add confirmed primes to the list in a threadsafe way?


Solution

  • You would simply call AList.Add(AIndex), and then Sort() the list after the loop is finished. But TList is not thread-safe, so you need a lock around the Add(), like a TCriticalSection or TMutex:

    procedure TForm1.Button1Click(Sender: TObject);
    var
      i: Integer;
      AList: TList<Integer>;
      ALock: TCriticalSection;
      LoopResult: TParallel.TLoopResult;
    begin
      AList := TList<Integer>.Create;
      ALock := TCriticalSection.Create;
    
      TParallel.&For(1, 100000,
        procedure(AIndex: Integer)
        begin
          if IsPrime(AIndex) then
          begin
            ALock.Enter;
            try
              AList.Add(AIndex);
            finally
              ALock.Leave;
            end;
          end;
        end);
    
      AList.Sort;
      for i := 0 to AList.Count-1 do
      begin
        Memo1.Lines.Add(IntToStr(AList[i]));
      end;
    
      ALock.Free;
      AList.Free;
    end;
    

    Or use TThreadList<T> instead:

    procedure TForm1.Button1Click(Sender: TObject);
    var
      i: Integer;
      AList: TThreadList<Integer>;
      LList: TList<Integer>;
      LoopResult: TParallel.TLoopResult;
    begin
      AList := TThreadList<Integer>.Create;
    
      TParallel.&For(1, 100000,
        procedure(AIndex: Integer)
        begin
          if IsPrime(AIndex) then
          begin
            AList.Add(AIndex);
          end;
        end);
    
      LList := AList.LockList;
      try
        LList.Sort;
        for i := 0 to LList.Count-1 do
        begin
          Memo1.Lines.Add(IntToStr(LList[i]));
        end;
      finally
        AList.UnlockList;
      end;
    
      AList.Free;
    end;