Jump to content
Steve Maughan

TThread.Synchronize in a Parallel.For Loop?

Recommended Posts

Hi Everyone,

 

This could well be a complete "noob" question as I'm not that experienced with the parallel library.

 

My objective is to utilize a parallel "for loop" in my optimization algorithm. Here's the code:

    TParallel.&For(0, fCriteria.Population - 1, procedure(i: integer)
      var
        xNode: TOptNode;
      begin

        //-- Get the latest node
        TThread.Synchronize(nil,
          procedure
          begin
            xNode := PopNode;
          end);

        //-- Get the new centers
        xNode.GenerateNewCenters;

        //-- Evaluate new centers
        xNode.Evaluate;

        //-- Push the node back onto the stack
        TThread.Synchronize(nil,
          procedure
          begin
            PushNode(xNode);
          end);
      end);

Each iteration of the loop tests a new scenario (xNode.GenerateNewCenters) and stores the best one found (xNode.Evaluate). The TOptNode holds all the data that needs to be optimized. It's a large object and takes significant time to be created.  So I've created a TStack of TOptNode object, and only create them when needed. I need to be able to "pop" the xNode objects off the stack and "push" them back on in a thread safe way. I would have thought the code above would work, instead it just freezes when it comes to the "for" loop.

 

What's the best way to "push" and "pop" in a thread safe manner inside of a parallel "for loop"?

 

Thanks,

 

Steve

Share this post


Link to post

Hard to say with incomplete code. We can only guess as to what PopNode and PushNode do, not to mention the code that operates on the nodes. 

Share this post


Link to post

Hi David,

 

PopNode and PushNode don't do anything special. I've listed the code below:

function TOptimizer.PopNode: TOptNode;
begin
  if fNodeStack.Count > 0 then
    result := fNodeStack.Pop
  else
  begin
    result := TOptNode.Clone(fMasterNode);
    fNodeList.Add(result);
  end;
end;

procedure TOptimizer.PushNode(xNode: TOptNode);
begin
  fNodeStack.Push(xNode);
end;

All they do is either pop the Node if one already exists on the stack, or create a new node if the stack is empty. Clearly I don't want multiple threads popping and pushing at the same time. That's why the main thread should do all of the popping and pushing. 

 

Thanks,

 

Steve

 

Share this post


Link to post

See attached for a simplified example . The procedure "ProcessSingleThreaded" works as expected, but the procedure "ProcessMultiThreaded" freezes the system:

procedure TForm1.ProcessMultiThreaded;
var
  xBest: integer;
  i: integer;
begin

  Screen.Cursor := crHourGlass;

  TParallel.&For(0, LoopSize - 1,
    procedure(j: integer)
    var
      xFoo: TFoo;
    begin

      //-- Get the Foo Object
      TThread.Synchronize(nil,
      procedure()
      begin
        xFoo := PopFoo;
      end);

      //-- Evaluate
      xFoo.Evaluate;

      //-- Push Back to the Stack
      TThread.Synchronize(nil,
      procedure()
      begin
        PushFoo(xFoo);
      end);
    end);

  xBest := 0;
  for i := 0 to FooList.Count - 1 do
    xBest := Max(xBest, FooList[i].BestValue);

  Form1.Caption := IntToStr(xBest);

  Screen.Cursor := crDefault;
end;

Why is the TThread.Synchronize freezing the application?

 

All help appreciated,

 

Thanks,

 

Steve

Synchronize.zip

Share this post


Link to post

It could still be related to the code that you have, which we don't have. 

 

Anyway, it's a bad idea to force the code onto the main thread just to serialise it. Use a lock instead. 

Share this post


Link to post
2 minutes ago, David Heffernan said:

It could still be related to the code that you have, which we don't have. 

 

Anyway, it's a bad idea to force the code onto the main thread just to serialise it. Use a lock instead. 

Hi David,

 

Thanks for the input but I've just provided a complete example. See attached above. You have all the code.

 

Based on your comment are you suggesting I mark the push and pop as Critical Sections? I haven't locked code before so please excuse my ignorance.

 

Thank again,

 

Steve

 

Share this post


Link to post

As for that zip file, you synchronise the pop but not the push. 

 

Anyway, replace the code with a lock and take it from there. 

Share this post


Link to post
13 hours ago, Steve Maughan said:

Hi Everyone,

 

This could well be a complete "noob" question as I'm not that experienced with the parallel library.

 

My objective is to utilize a parallel "for loop" in my optimization algorithm. Here's the code:


    TParallel.&For(0, fCriteria.Population - 1, procedure(i: integer)
      var
        xNode: TOptNode;
      begin

        //-- Get the latest node
        TThread.Synchronize(nil,
          procedure
          begin
            xNode := PopNode;
          end);

        //-- Get the new centers
        xNode.GenerateNewCenters;

        //-- Evaluate new centers
        xNode.Evaluate;

        //-- Push the node back onto the stack
        TThread.Synchronize(nil,
          procedure
          begin
            PushNode(xNode);
          end);
      end);

Each iteration of the loop tests a new scenario (xNode.GenerateNewCenters) and stores the best one found (xNode.Evaluate). The TOptNode holds all the data that needs to be optimized. It's a large object and takes significant time to be created.  So I've created a TStack of TOptNode object, and only create them when needed. I need to be able to "pop" the xNode objects off the stack and "push" them back on in a thread safe way. I would have thought the code above would work, instead it just freezes when it comes to the "for" loop.

 

What's the best way to "push" and "pop" in a thread safe manner inside of a parallel "for loop"?

 

If you execute the TParallel.For loop in the main thread then it would probably block the Synchronize call, since that tries to execute code in the main thread. The code can only be executed when the main thread reaches a "safe" state, which, in a typical VCL application, is the message loop. Your code does not get there until the complete loop has been finished.

 

What you need here is a thread-safe stack class, or you have to use a standard TStack<T> in a thread-safe manner, i. e. use a TCriticalSection object, which you enter before each pop or push call on the stack and leave when the call has returned (using try finally!). The loop will still block your main thread until it is done, though.

Share this post


Link to post
56 minutes ago, PeterBelow said:

If you execute the TParallel.For loop in the main thread then it would probably block the Synchronize call, since that tries to execute code in the main thread. The code can only be executed when the main thread reaches a "safe" state, which, in a typical VCL application, is the message loop. Your code does not get there until the complete loop has been finished.

 

What you need here is a thread-safe stack class, or you have to use a standard TStack<T> in a thread-safe manner, i. e. use a TCriticalSection object, which you enter before each pop or push call on the stack and leave when the call has returned (using try finally!). The loop will still block your main thread until it is done, though.

So TParallel doesn't service the synchronize queue?

Share this post


Link to post

I think I've solved it by using a TThreadList object as pseudo stack (FooList in the code below). This was something new for me. Previously I mistakenly thought "TThreadList" was a list of threads, whereas it's really a generic thread-safe list.

 

The thread-safe pop and push procedures are as follows:

function TForm1.PopFoo: TFoo;
var
  xList: TList<TFoo>;
begin
  xList := FooList.LockList;
  try
    if xList.Count > 0 then
    begin
      result := xList.Last;
      xList.Delete(xList.Count - 1);
    end
    else
      result := TFoo.Create;
  finally
    FooList.UnlockList;
  end;
end;

procedure TForm1.PushFoo(xFoo: TFoo);
begin
  FooList.Add(xFoo);
end;

In case anyone is interested I've attached the full source of the example project.

 

Thanks for the help,

 

Steve

Synchronize2.zip

Edited by Steve Maughan

Share this post


Link to post
3 hours ago, David Heffernan said:

So TParallel doesn't service the synchronize queue?

I have not checked the source in this case but the symptoms reported by the OP seem to indicate that it just blocks until all tasks launched by the for loop have completed.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×