【发布时间】:2014-12-17 21:20:00
【问题描述】:
给定以下在一维数组中查找奇数的简单任务:
begin
odds := 0;
Ticks := TThread.GetTickCount;
for i := 0 to MaxArr-1 do
if ArrXY[i] mod 2 = 0 then
Inc(odds);
Ticks := TThread.GetTickCount - Ticks;
writeln('Serial: ' + Ticks.ToString + 'ms, odds: ' + odds.ToString);
end;
看起来这将是并行处理的理想选择。因此,人们可能会想使用以下 TParallel.For 版本:
begin
odds := 0;
Ticks := TThread.GetTickCount;
TParallel.For(0, MaxArr-1, procedure(I:Integer)
begin
if ArrXY[i] mod 2 = 0 then
inc(odds);
end);
Ticks := TThread.GetTickCount - Ticks;
writeln('Parallel - false odds: ' + Ticks.ToString + 'ms, odds: ' + odds.ToString);
end;
这种并行计算的结果在两个方面有点令人惊讶:
计算的赔率数有误
执行时间比串口版本长
1) 是可以解释的,因为我们没有保护并发访问的几率变量。所以为了解决这个问题,我们应该改用TInterlocked.Increment(odds);。
2) 也是可以解释的:表现出false sharing的效果。
理想情况下,错误共享问题的解决方案是使用局部变量来存储中间结果,并且仅在所有并行任务结束时汇总这些中间结果。 这是我无法理解的真正问题:有没有办法将局部变量放入我的匿名方法中?请注意,在匿名方法体中简单地声明一个局部变量是行不通的,因为每次迭代都会调用匿名方法体。如果这在某种程度上可行,是否有办法在每次任务迭代结束时从匿名方法中获取我的中间结果?
编辑:我实际上对计算赔率或埃文并不感兴趣。我只是用这个来演示效果。
为了完整起见,这里有一个控制台应用程序来展示效果:
program Project4;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.Threading, System.Classes, System.SyncObjs;
const
MaxArr = 100000000;
var
Ticks: Cardinal;
i: Integer;
odds: Integer;
ArrXY: array of Integer;
procedure FillArray;
var
i: Integer;
j: Integer;
begin
SetLength(ArrXY, MaxArr);
for i := 0 to MaxArr-1 do
ArrXY[i]:=Random(MaxInt);
end;
procedure Parallel;
begin
odds := 0;
Ticks := TThread.GetTickCount;
TParallel.For(0, MaxArr-1, procedure(I:Integer)
begin
if ArrXY[i] mod 2 = 0 then
TInterlocked.Increment(odds);
end);
Ticks := TThread.GetTickCount - Ticks;
writeln('Parallel: ' + Ticks.ToString + 'ms, odds: ' + odds.ToString);
end;
procedure ParallelFalseResult;
begin
odds := 0;
Ticks := TThread.GetTickCount;
TParallel.For(0, MaxArr-1, procedure(I:Integer)
begin
if ArrXY[i] mod 2 = 0 then
inc(odds);
end);
Ticks := TThread.GetTickCount - Ticks;
writeln('Parallel - false odds: ' + Ticks.ToString + 'ms, odds: ' + odds.ToString);
end;
procedure Serial;
begin
odds := 0;
Ticks := TThread.GetTickCount;
for i := 0 to MaxArr-1 do
if ArrXY[i] mod 2 = 0 then
Inc(odds);
Ticks := TThread.GetTickCount - Ticks;
writeln('Serial: ' + Ticks.ToString + 'ms, odds: ' + odds.ToString);
end;
begin
try
FillArray;
Serial;
ParallelFalseResult;
Parallel;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
【问题讨论】:
-
设置和调用所有这些匿名方法比执行方法花费的时间要多得多。所以“虚假分享”并不是这里真正的问题。正如您所发现的,必须使用 interlockedincrement,这也会使进程停止。至于存储中间体,您可以使用全局数组。不过,在这种情况下,更倾向于使用普通的单线程解决方案。
-
Map reduce 是你想要的。尽管这项任务如此琐碎,但线程开销将占主导地位
-
@LURD,如何从我的匿名方法中访问全局数组?你能举个例子吗?
-
你有索引,只需用它来存储每次迭代的结果。我假设您的实际任务与此示例不同。或遵循 Davids 的建议,Is there a MapReduce library for Delphi?。
-
@LURD 事实上,我根本不是真正的任务。您可以将我的问题视为学术问题。抱歉,我如何使用索引来存储我的结果?你能告诉我一些代码吗,因为我不明白。
标签: multithreading delphi parallel-processing delphi-xe7