访问线程使用的对象时减少开销
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;
如果您想使用配置启动工作线程,那么您应该对其进行克隆,然后将克隆传递给工作线程,因为它可以更改。
线程每秒循环遍历 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;
如果您想使用配置启动工作线程,那么您应该对其进行克隆,然后将克隆传递给工作线程,因为它可以更改。