在 Delphi 中使用 TMonitor 阻止对全局对象的访问

Using TMonitor in Delphi to block access to global object

提问人:David Moorhouse 提问时间:11/16/2023 更新时间:11/16/2023 访问量:126

问:

我在 Delphi 中有一个全局对象,它从磁盘中读取一些对象定义,并创建一个表示它们的工作器对象的缓存。
全局对象上还有一个方法,它返回工作器对象的实例 - 如果全局对象仍在读取磁盘/创建工作器的方法中,则需要阻止此方法。此 load/create 方法将间歇性地(每天一次)调用,以强制重新加载 xml 文件。

我创建了一个简化版本的代码,以便我可以获得一些关于正确使用 TMonitor 记录的反馈。 首先是声明

type
  // a simplified version of the worker, I just get it to return the timestamp of when it was created - in real life this actually does something :)
  TWorker = class
  private
    FValue: TDateTime;
  public
    property Value: TDatetime read FValue;
  end;

// An interface modelling the global object
  IStorage = interface
    ['{8FD599BE-4064-45DE-8FFC-96A9D2C812F1}']
    function GetWorker: TWorker;
    procedure ReLoad;
  end;

// a global pointer to a function that has access to a singleton instance of the worker. Creating this 
var
  Storage: function: IStorage;

现在实施

// a concrete implementation of the interface
type
  TStorage = class(TInterfacedObject, IStorage)
  private
    FIsLoaded: Boolean;  // flag to indicate if objects have been loaded
    FList: TObjectList<TWorker>;  // list of Worker objects (takes ownership)
  protected
    { IStorage }
    function GetWorker: TWorker;
    procedure Load;
  public 
    constructor Create;
    destructor Destroy; override;
  end;

// the method to "load/reload" from disk - I've just simplified this-in real life it would lock then clear the cache and recreate the objects
procedure TStorage.ReLoad;
begin
  TMonitor.Enter(Self);
  try
    FIsLoaded := False;
    for var I := 0 to 15 do
      Sleep(250);
    FList.Clear;
    FIsLoaded := true;
    TMonitor.PulseAll(Self);
  finally
    TMonitor.Exit(Self);
  end;
end;

// and the method that serves out instances of worker objects
// this is the one that is causing me to double check my understanding of TMonitor
function TStorage.GetWorker: TWorker;
begin
  if not FIsLoaded then
  begin
    TMonitor.Enter(Self);
    try
      while not FIsLoaded do
        TMonitor.Wait(Self, INFINITE);
    finally
      TMonitor.Exit(Self);
    end;
  end;
  Result := TWorker.Create;
  Result.FValue := Now;
end;

我正在使用 TMonitor 实例(锁定全局对象)来阻止调用线程继续使用 GetWorker,直到布尔值 FIsLoaded 设置为 true。

我走在正确的轨道上吗?特别是我使用的 Wait 和 PulseAll 方法。

如果这有什么不同,我正在使用 Delphi 11.3,因为谷歌搜索表明使用 TMonitor 存在一些问题,但大约 10 年前。

德尔福 螺纹安全 Tmonitor

评论

0赞 David Moorhouse 11/16/2023
我刚刚查看了有关在类级变量上使用 Volatile 属性的文档,我认为它应该应用于 FIsLoaded 以确保不执行优化,因此无论哪个线程正在更改它(通过 Reload 方法),对它的更改都作为原子操作进行处理。

答:

1赞 Allen Bauer 11/16/2023 #1

你走在正确的轨道上。我建议对锁使用私有实例字段而不是 Self。这将远离“锁定这个(自我)是坏的”问题。否则,一些外部“不良行为者”可能会占用您在内部使用的相同锁。

如果你有 System.pas 源代码,应该有一个相当大的注释来描述 TMonitor,包括一个描述其灵感的文章的链接。