在Delphi 10.1柏林中,我想做一个TParallel.&For
循环响应。
我有一个并行循环,类似于有问题的 TParallel.For 中的示例:将值存储在 TList 中,同时在 TParallel.For 循环中计算。循环计算值并将这些值存储在TList<Real>
中。
我尝试使用 TTask.Run
在单独的线程中运行TParallel.&For
以使其响应:
type
TCalculationProject=class(TObject)
private
Task: ITask;
...
public
List: TList<Real>;
...
end;
function TCalculationProject.CalculateListItem(const AIndex: Integer): Real;
begin
//a function which takes a lot of calculation time
//however in this example we simulate the calculation time and
//use a simple alogorithm to verify the list afterwards
Sleep(30);
Result:=10*AIndex;
end;
procedure TCalculationProject.CalculateList;
begin
List.Clear;
Task:=TTask.Run(
procedure
var
LoopResult: TParallel.TLoopResult;
Res: Real;
Lock: TCriticalSection;
begin
Lock:=TCriticalSection.Create;
try
LoopResult:=TParallel.&For(0, 1000-1,
procedure(AIndex: Integer; LoopState: TParallel.TLoopState)
begin
Res:=CalculateListItem(AIndex);
Lock.Enter;
try
List.Add(Res);
finally
Lock.Leave;
end;
end
);
finally
Lock.Free;
end;
if LoopResult.Completed then
begin
TThread.Synchronize(TThread.Current,
procedure
begin
SortList;
ShowList;
end
);
end;
end
);
end;
问题是列表随机不正确:列表中存在重复值。例如:
list item 0: 0
list item 1: 10
list item 2: 20
list item 3: 20 <- incorrect
list item 4: 20 <- incorrect
list item 5: 50
....
除了Lock.Enter
Lock.Leave
部分,我还尝试了Synchronize
TThread.Synchronize(TThread.Current,
procedure
begin
List.Add(Res);
end
);
或
TThread.Synchronize(nil,
procedure
begin
List.Add(Res);
end
);
和Queue
TThread.Queue(TThread.Current,
procedure
begin
List.Add(Res);
end
);
或
TThread.Queue(nil,
procedure
begin
List.Add(Res);
end
);
但问题仍然存在。我做错了什么?
Parallel.For
循环中的所有线程共享Res
变量。当线程即将将Res
值存储到列表中时,它可能已被一个或多个线程更改。换句话说,Res
的值在将其放入列表时是不可预测的。
通过将Res
设置为每个线程的本地来修复它。
至于哪种方法最好,我建议进行性能比较。@Ken的建议似乎也是一个不错的主意。避免锁定通常是获得良好性能的秘诀。
此外,还要与没有线程的循环进行比较。