TInterlocked.CompareExchange是否支持接口对象?多线程单例疑问
ISAPI多线程DLL中接口单例懒加载的线程安全问题与解决方案
问题背景
在ISAPI多线程DLL环境下实现接口单例的懒加载,初始代码存在线程安全风险,使用AtomicCmpExchange重构后,发现创建的对象会因超出作用域被销毁。
初始线程不安全代码
... interface var FHIRResourceFactory: function: IFHIRResourceFactory; implementation ... // global var and function var _FHIRFactory: IFHIRResourceFactory; function GetFHIRResourceFactory: IFHIRResourceFactory; begin if (_FHIRFactory = nil) then { The object doesn't exist yet. Create one. } _FHIRFactory := TFHIRResourceFactory.Create; Result := _FHIRFactory; end; initialization FHIRResourceFactory := GetFHIRResourceFactory; end.
重构后的线程安全尝试代码
... interface var FHIRResourceFactory: function: IFHIRResourceFactory; implementation ... // global var and function var _FHIRFactory: IFHIRResourceFactory; function GetFHIRResourceFactory: IFHIRResourceFactory; var newFHIRFactory: IFHIRResourceFactory; begin if (_FHIRFactory = nil) then begin { The object doesn't exist yet. Create one. } newFHIRFactory := TFHIRResourceFactory.Create; { It's possible another thread also created one. Only one of us will be able to set the _FHIRFactory singleton variable } if AtomicCmpExchange(Pointer(_FHIRFactory), Pointer(newFHIRFactory), nil) <> nil then begin { The other beat us. Destroy our newly created object and use theirs. } newFHIRFactory := nil; end; // if I don't add this line the object will fall out of scope and be destroyed // _FHIRFactory._AddRef end; Result := _FHIRFactory; end; initialization FHIRResourceFactory := GetFHIRResourceFactory; end.
解决方案与问题解析
核心问题原因
你遇到的对象销毁问题,本质是接口引用计数在原子操作中未被正确维护:
- 当把接口类型转换为
Pointer传入AtomicCmpExchange时,不会自动触发接口的_AddRef操作。 - 若你的线程在竞态中成功设置
_FHIRFactory,newFHIRFactory出作用域时会执行_Release,此时_FHIRFactory指向的对象引用计数会减至0,导致对象被销毁。
是否有支持接口的TInterlocked.CompareExchange?
Delphi没有提供直接针对接口类型的TInterlocked.CompareExchange重载,因为接口的内部结构包含指针和虚表,原子操作仅能针对指针部分,引用计数的维护必须手动处理。以下是两种可靠的替代方案:
方案一:手动维护引用计数的原子操作
用指针变量存储对象实例,配合原子操作手动管理引用计数:
var _FHIRFactoryPtr: Pointer; // 存储对象指针,而非接口变量 function GetFHIRResourceFactory: IFHIRResourceFactory; var newObj: TFHIRResourceFactory; existingPtr: Pointer; begin if _FHIRFactoryPtr = nil then begin newObj := TFHIRResourceFactory.Create; try // 提前给新对象加引用,避免后续转换为接口时的计数问题 newObj._AddRef; // 原子比较交换指针 existingPtr := AtomicCmpExchange(_FHIRFactoryPtr, Pointer(newObj), nil); if existingPtr <> nil then begin // 竞态失败,释放自己创建的对象 newObj._Release; // 将已有指针转为接口返回 Result := IFHIRResourceFactory(existingPtr); end else begin // 竞态成功,将新对象转为接口返回 Result := newObj; end; except // 创建失败时直接释放对象 newObj.Free; raise; end; end else begin // 将已有指针转为接口,自动触发_AddRef Result := IFHIRResourceFactory(_FHIRFactoryPtr); end; end;
方案二:双重检查锁定(临界区实现)
这种方式在单例创建完成后,后续调用不会进入临界区,性能损失可忽略,且完全避免引用计数问题:
type TFHIRResourceFactory = class(TInterfacedObject, IFHIRResourceFactory) public class function Instance: IFHIRResourceFactory; static; // 接口方法声明 end; var _Instance: IFHIRResourceFactory; _InstanceLock: TRTLCriticalSection; class function TFHIRResourceFactory.Instance: IFHIRResourceFactory; begin if _Instance = nil then begin EnterCriticalSection(_InstanceLock); try // 二次检查确保未被其他线程创建 if _Instance = nil then _Instance := TFHIRResourceFactory.Create; finally LeaveCriticalSection(_InstanceLock); end; end; Result := _Instance; end; initialization InitializeCriticalSection(_InstanceLock); finalization DeleteCriticalSection(_InstanceLock);
内容的提问来源于stack exchange,提问作者David Moorhouse
相关产品推荐
相关产品推荐

