Delphi多线程计时异常:为何多线程执行耗时高于单线程?
问题背景
在戴尔3571(20核/线程i9)设备上测试并发线程的执行速度,发现结果不符合预期,疑似存在锁机制影响。尝试使用Windows CreateThread、ITask等不同线程API测试,结果一致。
测试代码
unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type TMyThread = class(TThread) public procedure Execute; override; end; TForm1 = class(TForm) Memo1: TMemo; procedure FormCreate(Sender: TObject); private { Private declarations } public { Public declarations } procedure Log(Sender: TMyThread; Log: string); end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Log(Sender: TMyThread; Log: string); begin Memo1.Lines.add(Log); end; procedure TForm1.FormCreate(Sender: TObject); var Thr: array[0..19] of TMyThread; begin for var t := 0 to 10 do begin var Thread := TMyThread.Create(True); Thr[t] := Thread; Thread.Priority := TPHigher; end; for var t := 0 to 10 do Thr[t].Resume; end; { MyThread } procedure TMyThread.Execute; begin Sleep(500); try var ii: nativeint; var Start := GetTickCount; for var i := 0 to 750000000 do inc(ii); var Delta := (GetTickCount - Start); Synchronize( procedure begin Form1.Log(Self, Format( 'Done Loading : %dms', [Delta]) ); end ); except asm nop; end; end; end; end.
测试结果
- 单线程执行时,单次循环耗时:320ms
- 10线程并发执行时,各线程耗时:
Done Loading : 344ms
Done Loading : 375ms
Done Loading : 391ms
Done Loading : 422ms
Done Loading : 438ms
Done Loading : 469ms
Done Loading : 469ms
Done Loading : 469ms
Done Loading : 516ms
Done loading : 531ms
疑问
- 多线程执行时各线程耗时是否应接近320ms?
- 为什么使用不同线程API(TThread、CreateThread、ITask)测试结果一致?
原因分析
1. 线程调度与上下文切换开销
即使是20核/线程的CPU,10个线程同时运行时,Windows调度器无法保证所有线程持续占用核心不被切换。线程切换需要保存/恢复寄存器状态、刷新缓存,这些操作会消耗额外时间,导致每个线程的总执行时长增加。另外,你将线程优先级设为TPHigher,会让调度器更频繁地调度这些线程,但依然无法完全避免上下文切换的开销。
2. 系统调用的隐性竞争
测试中使用的GetTickCount是系统内核函数,调用时需要从用户态切换到内核态。单线程时这类切换次数少,开销不明显;但多线程同时调用系统函数时,内核态的资源竞争会增加每个调用的耗时,间接拉高了循环的总计时。
3. CPU缓存与内存带宽竞争
虽然每个线程的ii是栈上私有变量,不存在共享锁,但多个线程同时执行内存读写操作(栈内存也属于系统内存),会占用更多内存总线带宽。当带宽饱和时,每个线程的内存访问延迟会增加,进而影响循环执行速度。此外,CPU的缓存一致性协议(如MESI)在多核心场景下,即使操作私有数据,也可能存在隐性的缓存同步开销。
4. 不同线程API的本质一致性
TThread、CreateThread、ITask最终都是依赖Windows内核的线程调度机制实现的,本质都是操作系统层面的线程实体。它们面临的调度规则、资源限制完全相同,因此测试结果不会有明显差异。
结论
多线程执行时各线程耗时不会接近单线程的320ms,无锁的独立计算也会受到线程调度、系统调用开销、内存带宽竞争等隐性因素影响,这些都是多线程环境下的正常现象,并非锁机制导致。
内容的提问来源于stack exchange,提问作者Laurent Meyer

