You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi多线程并行启动与线程安全数据收集问题咨询

Delphi多线程并行执行与线程安全问题解答

一、解决线程串行执行问题

你的代码里线程串行的核心原因有两个:

  1. 在TMyThread的构造函数里直接调用了self.Execute;——这会让Execute方法在主线程里同步执行,而非交给新创建的线程异步运行。
  2. 创建线程时使用Create(true, n)将线程设为挂起状态,但没有调用Start(Delphi XE及以后版本)或Resume(旧版本)启动线程,反而直接读取日志,相当于主线程必须等当前线程的Execute执行完才会进入下一次循环。

修复方案

1. 修正TMyThread的构造函数

移除手动调用Execute的代码,让线程启动后自动执行该方法:

constructor TMyThread.Create(createsuspended: boolean; ilimit:longint);
begin
  inherited Create(createsuspended);
  self.fLog := TStringList.Create;
  self.fiLimit := ilimit;
  self.fExecutionTime := 0;
  // 移除 self.execute; 这行代码
  // 若需要线程创建后自动启动,可将createsuspended设为false,或在这里调用Start
  // 示例:if not createsuspended then Start;
end;

2. 主线程批量启动线程并等待全部完成

先保存所有线程引用,启动后等待所有线程执行完毕,再统一收集结果:

procedure TForm1.Button1Click(Sender: TObject);
var 
  n,i:longint;
  threads: array of TMyThread;
begin
  SetLength(threads, 6);
  // 第一步:创建并启动所有线程
  for i := 0 to 5 do
  begin
    case i of
      0: n:=100000;
      1: n:=1000000;
      2: n:=10000000;
      3: n:=100000000;
      4: n:=10000000;
      5: n:=1000000000;
    end;
    threads[i] := TMyThread.Create(true, n);
    threads[i].Start; // 启动线程(旧版Delphi替换为Resume)
  end;

  // 第二步:等待所有线程完成并收集结果
  for i := 0 to High(threads) do
  begin
    threads[i].WaitFor; // 等待单个线程结束
    memo1.Lines.AddStrings(threads[i].fLog);
    memo1.Lines.Add('------------------------------------');
    threads[i].Free; // 释放线程资源
  end;
end;

更简洁的方案:使用TTask(Delphi XE6+)

如果你的Delphi版本支持并行库,用TTask可以省去手动管理线程的繁琐:

uses System.Threading;

procedure TForm1.Button1Click(Sender: TObject);
var
  tasks: array of ITask;
  logs: array of TStringList;
  i,n: Integer;
begin
  SetLength(tasks, 6);
  SetLength(logs, 6);

  for i := 0 to 5 do
  begin
    logs[i] := TStringList.Create;
    case i of
      0: n:=100000;
      1: n:=1000000;
      2: n:=10000000;
      3: n:=100000000;
      4: n:=10000000;
      5: n:=1000000000;
    end;
    // 创建并启动异步任务
    tasks[i] := TTask.Run(
      procedure
      var
        number, j: Integer;
        startTime: TDateTime;
      begin
        startTime := Now;
        logs[i].Add(AddDotsAndLeftJ(35,'.','Started task '+IntToStr(TThread.CurrentThread.ThreadID)+' at ')+': ' + FormatDateTime('h:nn:ss:zzz', Now));
        logs[i].Add(AddDotsAndLeftJ(35,'.','Limit: ')+': ' + IntToStr(n));

        number := 0;
        for j := 0 to n do
        begin
          number := number + Random(5);
        end;

        logs[i].Add(AddDotsAndLeftJ(35,'.','Sum: ')+': ' + IntToStr(number));
        logs[i].Add(AddDotsAndLeftJ(35,'.','Finished task '+IntToStr(TThread.CurrentThread.ThreadID)+' at ')+': ' + FormatDateTime('h:nn:ss:zzz', Now));
        logs[i].Add(AddDotsAndLeftJ(35,'.','Task execution time')+': ' + FormatDateTime('h:nn:ss:zzz', Now - startTime));
      end
    );
  end;

  // 等待所有任务完成
  TTask.WaitForAll(tasks);

  // 汇总结果
  for i := 0 to High(logs) do
  begin
    memo1.Lines.AddStrings(logs[i]);
    memo1.Lines.Add('------------------------------------');
    logs[i].Free;
  end;
end;

二、动态数组独立元素写入的线程安全性

这种操作是线程安全的,原因如下:

  • 只要在所有线程启动前就通过SetLength固定了动态数组的长度,数组的内存布局就不会再变化,每个元素的内存地址独立且固定。
  • 每个线程仅写入自己对应的索引位置,不同线程操作的是完全独立的内存块,不会产生内存竞争或数据覆盖,无需额外加锁或同步机制。

需要注意两个细节:

  1. 线程运行期间绝对不要修改数组长度(再次调用SetLength),这会导致数组重新分配内存,原元素地址失效,可能引发访问错误或数据混乱。
  2. 确保每个线程只访问自己分配的索引,不要越界写入其他线程的元素位置。

内容的提问来源于stack exchange,提问作者Vlad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 04:27:33