访问线程使用的对象时减少开销

Reducing overhead when accessing objects used by thread

线程每秒循环遍历 1000 个对象的列表。 这些对象保存简单的配置数据。当满足某些条件时,工作线程将获得配置数据并根据该数据执行一些工作。

现在我想打开一个带有配置数据的设置对话框,这样我就可以更改此类对象中的数据。但是当线程也在不断访问它们时,我必须访问列表中的对象。我知道如何使用临界区,但如果线程每次检查对象时都进入临界区,那么临界区每秒将进入和离开 1000 次。也许有更聪明的方法?

在以下情况下如何以最少的开销实现线程安全:

a) 将配置数据加载到设置对话框表单中(在虚拟模式下使用 TListView 并需要按需访问对象列表)

b) 并将表单输入保存回对象?

编辑:要求提供更多详细信息。

对象在 TList 中,基本上是这样的:

TConfigData = class
  ID:Integer;
  Name: String;
  SwitchTime: TDateTime;
end;

需要将 ConfigData 对象的数据加载到设置对话框表单中,以便对其进行编辑,然后,如果用户单击“确定”,则应更新 ConfigData 对象,线程将愉快地使用此新数据下次访问对象时。但是,不能在线程读取 ConfigData 对象的同时进行更新。

编辑 2:其他详细信息:

线程正在读取 ID、名称和 SwitchTime,但只有 SwitchTime 被线程更改。 (工作完成后,将计算新的时间,这就是触发下一个工作事件的原因)。

设置对话框可以更改 Name 和 SwitchTime,但不能更改 ID。

主线程(伪)代码(ObjList为全局变量):

if ConfigUpdated(i) then
  ObjList[i].ConfigVersion := ObjList[i].ConfigVersion + 1;

其他线程(伪)代码(ObjConfVer 是线程的本地)

for i := 0 to ObjCount - 1 do
  if ObjConfVer[i] < ObjList[i].ConfigVersion then
  begin
    // Here you have to take care that main thread will can not change config  while you handling it
    // Do something about new config
    ObjConfVer[i] := ObjList[i].ConfigVersion;
    // Exit from critical section
  end;

如果您有 n 个使用相同 ObjList 的线程,则允许每个线程独立地对已更改的配置执行某些操作。

顺便说一句,如果您使用的是 FPC/Lazarus,这个 link 可能会有用:Parallel procedures

经过一番思考,您完全可以通过使用 InterlockedExchangePointer 而无需使用临界区:

您需要添加例程来更新项目的配置:

procedure TMyThread.UpdateConfig(const aIndex: Integer; const aID:Integer;
    const aName: String; const aSwitchTime: TDateTime);
var
  newConfigToEdit: TConfigData;
  oldValue: TConfigData;
begin
  newConfigToEdit := TConfigData.Create;
  newConfigToEdit.ID := aID;
  newConfigToEdit.Name := aName;
  newConfigToEdit.SwitchTime := aSwitchTime;
  repeat
    oldvalue := InterlockedExchangePointer(FConfigDataList.List[aIndex], newConfigToEdit);
  until (oldvalue <> nil) and (oldValue <> newConfigToEdit);
  oldvalue.Free; // Free the replaced one
end;

该例程将替换索引为 aIndex 的项目的配置。要在您的线程中获取配置,您需要有点聪明。我们得到它的一个副本,并在处理它时用 nil 替换列表中的值。这可以防止其他线程替换它。完成后,我们放回替换值。

procedure TMyThread.Execute;
var
  configToUse: TConfigData;
begin
  repeat
    // Get the config and replace it with nil so it won't be changed
    configToUse := InterlockedExchangePointer(FConfigDataList.List[idx], nil);
    // Do stuff with the config

    // Put the config back
    FConfigDataList.List[idx] := configToUse;
    // You could also use the line below instead of the assignment
    // InterlockedExchangePointer(FConfigDataList.List[idx], configToUse);
  until Terminated;
end;

如果您想使用配置启动工作线程,那么您应该对其进行克隆,然后将克隆传递给工作线程,因为它可以更改。