【问题标题】:MultiThreading and TCriticalSection on kylixkylix 上的多线程和 TCriticalSection
【发布时间】:2026-02-21 21:55:01
【问题描述】:

我在 Delphi 7 中维护一个应用程序,它有一个可以用 CrossKylix 编译的服务器部分。对于性能问题,我正在测试多线程和关键部分的使用。

我创建了一个控制台应用程序,它创建 100 个 TThread,每个 TThread 计算一个斐波那契。然后我添加了一个临界区,以便一次只有一个线程计算斐波那契。正如预期的那样,如果没有关键部分,应用程序会更快。

然后我创建了一个控制台应用程序,它创建了 100 个 TThread,每个 TThread 在本地 TStringList 中添加单词并对该 TStringList 进行排序。然后我添加了一个关键部分,以便一次只执行一个线程。在 Windows 上,正如预期的那样,应用程序在没有关键部分的情况下运行得更快。在 Linux 上,CriticalSection 版本的运行速度比没有 CriticalSection 的版本快 2 倍。

Linux 上的 CPU 是具有 6 个内核的 AMD Opteron,因此应用应该受益于多线程。

有人可以解释为什么带有关键部分的版本更快吗?


编辑(添加一些代码)

线程创建和等待

tmpDeb := Now;
i := NBTHREADS;
while i > 0 do
begin
    tmpFiboThread := TFiboThread.Create(true);
    tmpFiboThread.Init(i, ParamStr(1) = 'Crit');
    Threads.AddObject(IntToStr(i), tmpFiboThread);
    i := i-1;
end;

i := 0;
while i < NBTHREADS do
begin
    TFiboThread(Threads.Objects[i]).Resume;
    i := i+1; 
end;

i := 0;
while i < NBTHREADS do
begin
    TFiboThread(Threads.Objects[i]).WaitFor;
    i := i+1; 
end;

WriteLn('Traitement total en : ' + inttostr(MilliSecondsBetween(Now, tmpDeb)) + ' milliseconds');

TThread 和关键部分使用

    type TFiboThread = class(TThread)
        private
            n : Integer;
            UseCriticalSection : Boolean;
        protected
            procedure Execute; override;

        public      
            ExecTime : Integer;

            procedure Init(n : integer; WithCriticalSect : Boolean);
    end;

var
  CriticalSection : TCriticalSection;

implementation

uses DateUtils;

function fib(n: integer): integer;
var
  f0, f1, tmpf0, k: integer;
begin
    f1 := n + 100000000;
    IF f1 >1 then
    begin
      k := f1-1;
      f0 := 0;
      f1 := 1;
      repeat
        tmpf0 := f0;
        f0 := f1;
        f1 := f1+tmpf0;
        dec(k);
      until k = 0;
    end
    else
      IF f1 < 0 then
        f1 := 0;
    fib := f1;
end;

function StringListSort(n: integer): integer;
var
  tmpSL : TStringList;
  i : Integer;
begin
    tmpSL := TStringList.Create;
    i := 0;
    while i < n + 10000 do
    begin
        tmpSL.Add(inttostr(MilliSecondOf(now)));
        i := i+1;
    end;
    tmpSL.Sort;

    Result := StrToInt(tmpSL.Strings[0]);
    tmpSL.Free;
end;

{ TFiboThread }

procedure TFiboThread.Execute;
var
  tmpStr : String;
  tmpDeb : TDateTime;
begin
    inherited;

    if Self.UseCriticalSection then
        CriticalSection.Enter;

    tmpDeb := Now;

    tmpStr := inttostr(fib(Self.n));
    //tmpStr := inttostr(StringListSort(Self.n));

    Self.ExecTime := MilliSecondsBetween(Now, tmpDeb);

    if Self.UseCriticalSection then
        CriticalSection.Leave;

    Self.Terminate;
end;

procedure TFiboThread.Init(n : integer; WithCriticalSect : Boolean);
begin
    Self.n := n;
    Self.UseCriticalSection := WithCriticalSect;
end;



initialization
    CriticalSection := TCriticalSection.Create;

finalization
    FreeAndNil(CriticalSection);

编辑 2

我读了这个why-using-more-threads-makes-it-slower-than-using-less-threads,据我了解,Linux 和 Kylix 编译的上下文切换比 win32 的上下文切换消耗更多的 CPU 资源。

【问题讨论】:

  • 你能展示你的演示代码吗?在最好的情况下,性能是一个棘手的主题。评论我们看不到的东西很棘手,而且容易出错。
  • 我的第一个猜测是为什么不同之处在于您的问题可能是缓存行争用。也就是说,您的线程是同时分配的,它们是否占用相同的缓存行将取决于堆分配器的行为。如果您确保每个 TThread 对象都有额外的未使用空间,使其大于处理器的缓存行,您可能会看到行为变化。我还没有检查过,但是您的 opteron 可能有 128 字节的缓存行,在这种情况下,每个缓存行可能有多个线程。
  • 我试图在我的 TFiboThread 和 SetLength(mybyteArray, 255) 中添加一个 TByteDynArray;在构造函数中,所以每个 TFiboThread 实例应该大于 255 字节,但它不会改变行为......

标签: multithreading performance delphi delphi-7 kylix


【解决方案1】:

排序字符串列表有很多内存分配,即调用内存管理器。内存管理器本身是线程安全的,这意味着它在内部使用了某种临界区。 因此,如果一百个线程在没有全局临界区的情况下同时运行,它们将不会调用 MM,这意味着不会使用内部锁(而不是全局临界区的一个锁)

这就是为什么纯斐波那契函数(没有字符串列表构建和排序)按预期工作 - 它没有内部隐藏锁

【讨论】: