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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 22:54:55