Steve Maughan 26 Posted May 30, 2019 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
David Heffernan 2345 Posted May 30, 2019 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
Steve Maughan 26 Posted May 31, 2019 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
Steve Maughan 26 Posted May 31, 2019 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
David Heffernan 2345 Posted May 31, 2019 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
Steve Maughan 26 Posted May 31, 2019 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
David Heffernan 2345 Posted May 31, 2019 There are other forms of locks that you might use. For cross platform code you'd use a monitor. Share this post Link to post
David Heffernan 2345 Posted May 31, 2019 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
PeterBelow 238 Posted May 31, 2019 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
David Heffernan 2345 Posted May 31, 2019 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
Steve Maughan 26 Posted May 31, 2019 (edited) 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 May 31, 2019 by Steve Maughan Share this post Link to post
PeterBelow 238 Posted May 31, 2019 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