Delphi多线程并行启动与线程安全数据收集问题咨询
Delphi多线程并行执行与线程安全问题解答
一、解决线程串行执行问题
你的代码里线程串行的核心原因有两个:
- 在
TMyThread的构造函数里直接调用了self.Execute;——这会让Execute方法在主线程里同步执行,而非交给新创建的线程异步运行。 - 创建线程时使用
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固定了动态数组的长度,数组的内存布局就不会再变化,每个元素的内存地址独立且固定。 - 每个线程仅写入自己对应的索引位置,不同线程操作的是完全独立的内存块,不会产生内存竞争或数据覆盖,无需额外加锁或同步机制。
需要注意两个细节:
- 线程运行期间绝对不要修改数组长度(再次调用
SetLength),这会导致数组重新分配内存,原元素地址失效,可能引发访问错误或数据混乱。 - 确保每个线程只访问自己分配的索引,不要越界写入其他线程的元素位置。
内容的提问来源于stack exchange,提问作者Vlad
相关产品推荐
相关产品推荐

