目前存在threadvar 的初始化时机不可控。如果需要扩展DelphiCrossSocket增加threadvar变量时,建议worker thread起来后,马上发送一个特殊的io到每个线程,线程处理这个io时,完成threadvar变量的初始化。建立HandleInit等虚方法。
一、当前问题
threadvar在 FPC 中保证零初始化(nil),所以当前代码恰好安全。但如果后续需要在 worker 线程初始化时做更复杂的事情(比如预分配 PerIoData 池、预创建 内存池、注册线程到诊断系统),没有初始化钩子就很麻烦。
threadvar初始化方案
Reactor 启动 worker 线程:
worker.Run()
→ 给自己 PostQueuedCompletionStatus(一个特殊的 "init" IO)
→ ProcessIoEvent 收到 "init" IO
→ _HandleInit: 初始化 threadvar (PerIoData 池预热、内存 池预热等)
→ 正常进入 recv/send 循环
优点:
确定性初始化:不依赖 threadvar 的零初始化保证,显式初始化每个 worker 线程的本地状态
预热:可以预分配 buffer/PerIoData/builder,避免第一批请求时的冷启动分配
诊断:可以记录每个 worker 线程的 ThreadID(替代 OnIoThreadBegin 回调)
可行性:完全可行。Cross-Socket 已有 TriggerIoThreadBegin/TriggerIoThreadEnd 回调(我们之前接入过)。但用 IO 投递更可靠——因为 TriggerIoThreadBegin 在 Execute 的 try 块内调用,此时线程已就绪但可能还没进入 IOCP 循环。
更简单的替代:其实不用投递特殊 IO——直接在 TriggerIoThreadBegin 回调里初始化即可。这个回调在每个 worker 线程启动时、在该线程的上下文中被调用(TIoEventThread.Execute → FOwner.TriggerIoThreadBegin(Self)),此时 threadvar 已经是该线程的副本:
pascal
procedure TMyIocpCrossSocket.OnIoThreadBegin(Sender: TObject; AIoThread: TIoEventThread);
begin
{ 在 worker 线程上下文中调用: 初始化该线程的 threadvar }
SrPioFreeList := nil; { 显式初始化 (虽然 FPC 保证零, 但更安全) }
SrPioFreeCount := 0;
SrLocalCache := nil; { buffer pool 线程缓存 }
{ 预热: 预分配几个 PerIoData 和 buffer }
PoolWarmUp;
end;
对比:
方案 初始化时机 预热 复杂度
TriggerIoThreadBegin 回调 worker 线程启动时 ✅ 可预分配 低(已有回调)
投递 "init" IO worker 线程进入 IOCP 循环后 ✅ 中(需定义特殊 IO 类型)
依赖 threadvar 零初始化 首次访问时(惰性) ❌ 冷启动 最低(当前)
我的建议:用 TriggerIoThreadBegin(已有回调,无需额外 IO 投递),在里面做显式初始化 + 预热。这比投递特殊 IO 更简单,且效果相同。
目前存在threadvar 的初始化时机不可控。如果需要扩展DelphiCrossSocket增加threadvar变量时,建议worker thread起来后,马上发送一个特殊的io到每个线程,线程处理这个io时,完成threadvar变量的初始化。建立HandleInit等虚方法。
一、当前问题
threadvar在 FPC 中保证零初始化(nil),所以当前代码恰好安全。但如果后续需要在 worker 线程初始化时做更复杂的事情(比如预分配 PerIoData 池、预创建 内存池、注册线程到诊断系统),没有初始化钩子就很麻烦。
threadvar初始化方案
Reactor 启动 worker 线程:
worker.Run()
→ 给自己 PostQueuedCompletionStatus(一个特殊的 "init" IO)
→ ProcessIoEvent 收到 "init" IO
→ _HandleInit: 初始化 threadvar (PerIoData 池预热、内存 池预热等)
→ 正常进入 recv/send 循环
优点:
确定性初始化:不依赖 threadvar 的零初始化保证,显式初始化每个 worker 线程的本地状态
预热:可以预分配 buffer/PerIoData/builder,避免第一批请求时的冷启动分配
诊断:可以记录每个 worker 线程的 ThreadID(替代 OnIoThreadBegin 回调)
可行性:完全可行。Cross-Socket 已有 TriggerIoThreadBegin/TriggerIoThreadEnd 回调(我们之前接入过)。但用 IO 投递更可靠——因为 TriggerIoThreadBegin 在 Execute 的 try 块内调用,此时线程已就绪但可能还没进入 IOCP 循环。
更简单的替代:其实不用投递特殊 IO——直接在 TriggerIoThreadBegin 回调里初始化即可。这个回调在每个 worker 线程启动时、在该线程的上下文中被调用(TIoEventThread.Execute → FOwner.TriggerIoThreadBegin(Self)),此时 threadvar 已经是该线程的副本:
pascal
procedure TMyIocpCrossSocket.OnIoThreadBegin(Sender: TObject; AIoThread: TIoEventThread);
begin
{ 在 worker 线程上下文中调用: 初始化该线程的 threadvar }
SrPioFreeList := nil; { 显式初始化 (虽然 FPC 保证零, 但更安全) }
SrPioFreeCount := 0;
SrLocalCache := nil; { buffer pool 线程缓存 }
{ 预热: 预分配几个 PerIoData 和 buffer }
PoolWarmUp;
end;
对比:
方案 初始化时机 预热 复杂度
TriggerIoThreadBegin 回调 worker 线程启动时 ✅ 可预分配 低(已有回调)
投递 "init" IO worker 线程进入 IOCP 循环后 ✅ 中(需定义特殊 IO 类型)
依赖 threadvar 零初始化 首次访问时(惰性) ❌ 冷启动 最低(当前)
我的建议:用 TriggerIoThreadBegin(已有回调,无需额外 IO 投递),在里面做显式初始化 + 预热。这比投递特殊 IO 更简单,且效果相同。