竞态条件 - 互斥量可以更灵活吗?
Race Condition - Can Mutexes Be More Flexible?
我有一个名为 TTestThread 的 TThread 对象。创建线程时,它们会制作全局配置变量的本地副本。用户可以随时更改全局配置变量,当他这样做时,所有线程都会通过其本地 UpdateLocalCopyOfConfigVariables 变量收到更改通知。线程不直接依赖于全局配置变量,因为用户可以随时修改它们,如果线程同时访问它们会产生竞争条件。
这里是 TTestThread:
type
TTestThread = class(TThread)
private
LocalConfigA : String;
LocalConfigB : Integer;
procedure UpdateLocalConfigIfNecessary;
protected
procedure Execute; override;
public
UpdateLocalCopyOfConfigVariables : Boolean;
constructor Create;
end;
implementation
constructor TTestThread.Create;
begin
inherited Create(false);
UpdateLocalCopyOfConfigVariables := true;
end;
procedure TTestThread.UpdateLocalConfigIfNecessary;
begin
WaitForSingleObject(ConfigurationLocker, INFINITE);
if (UpdateLocalCopyOfConfigVariables) then
begin
LocalConfigA := GlobalConfigA;
LocalConfigB := GlobalConfigB;
UpdateLocalCopyOfConfigVariables := false;
end;
ReleaseMutex(ConfigurationLocker);
end;
procedure TTestThread.Execute;
begin
while (not(Terminated)) do
begin
UpdateLocalConfigIfNecessary;
// Do stuff
end;
end;
如您所见,我有一个互斥锁来避免前面描述的那种竞争条件。 WaitForSingleObject(ConfigurationLocker, INFINITE); 当用户更改全局配置变量时调用:
procedure ChangeGlobalConfigVariables(const NewGlobalConfigA : String ; NewGlobalConfigB : Integer);
var
I : Integer;
begin
WaitForSingleObject(ConfigurationLocker, INFINITE);
GlobalConfigA := NewGlobalConfigA;
GlobalConfigB := NewGlobalConfigB;
for I := 0 to ThreadList.Count - 1 do
TTestThread(ThreadList[I]).UpdateLocalCopyOfConfigVariables := true;
ReleaseMutex(ConfigurationLocker);
end;
事实是,虽然它阻止了线程在更改全局配置变量的同时更新它们的本地配置变量副本,但它也阻止了两个线程同时更新它们的本地配置——甚至如果没有更改全局配置变量。据我所知,在进行写作时,竞争条件是一个问题。如果全局配置变量相同,那么所有线程都可以同时更新它们的本地副本而不会出现问题。
我说的对吗?如果是这样,有没有办法解决这个问题?当然,这不是一个大问题,但我仍然觉得必须有更好的解决方案...
无法仅在有写入权限时锁定代码。 展示了如何允许多个线程在写入锁定时读取变量的方法。
但是,读取全局配置对象应该是非常快的。所以这不是瓶颈。
如何更快地处理这个?
1.使用 TCriticalSection 而不是 mutex
临界区比互斥体快得多。互斥量可用于多个进程,而临界区仅在当前进程中工作。您不需要这种昂贵的锁定。用这样的变量交换 ConfigurationLocker
ConfigurationCriticalSection: TCrticalSection
。您需要在启动时创建对象,因为它用作全局变量。
您以类似的方式使用临界区。示例:
procedure TTestThread.UpdateLocalConfigIfNecessary;
begin
ConfigurationCriticalSection.Enter;
try
if (UpdateLocalCopyOfConfigVariables) then
begin
LocalConfigA := GlobalConfigA;
LocalConfigB := GlobalConfigB;
UpdateLocalCopyOfConfigVariables := false;
end;
finally
ConfigurationCriticalSection.Leave;
end;
end;
try..finally..end
模式在这里非常重要。如果你不使用它,当在 Enter
和 Leave
之间引发异常时,你可能会陷入死锁。当您使用带 WaitForSingleObject
和 ReleaseMutex
的互斥锁时,您需要应用相同的模式。
2。将新值的副本发送到线程
不要让线程访问全局配置。
有许多不同的模式可以通知其他对象有关更改的信息。一种方法是调用提供值的方法。这可能看起来像这样:
TTestThread(ThreadList[I]).UpdateLocalCopyOfConfigVariables(GlobalConfigA, GlobalConfigB);
所以对 TTestThread(ThreadList[I]).UpdateLocalCopyOfConfigVariables := true
的调用将被替换。
线程对象将存储新值并在特定情况下应用它们。
似乎是个好点
我有一个名为 TTestThread 的 TThread 对象。创建线程时,它们会制作全局配置变量的本地副本。用户可以随时更改全局配置变量,当他这样做时,所有线程都会通过其本地 UpdateLocalCopyOfConfigVariables 变量收到更改通知。线程不直接依赖于全局配置变量,因为用户可以随时修改它们,如果线程同时访问它们会产生竞争条件。
这里是 TTestThread:
type
TTestThread = class(TThread)
private
LocalConfigA : String;
LocalConfigB : Integer;
procedure UpdateLocalConfigIfNecessary;
protected
procedure Execute; override;
public
UpdateLocalCopyOfConfigVariables : Boolean;
constructor Create;
end;
implementation
constructor TTestThread.Create;
begin
inherited Create(false);
UpdateLocalCopyOfConfigVariables := true;
end;
procedure TTestThread.UpdateLocalConfigIfNecessary;
begin
WaitForSingleObject(ConfigurationLocker, INFINITE);
if (UpdateLocalCopyOfConfigVariables) then
begin
LocalConfigA := GlobalConfigA;
LocalConfigB := GlobalConfigB;
UpdateLocalCopyOfConfigVariables := false;
end;
ReleaseMutex(ConfigurationLocker);
end;
procedure TTestThread.Execute;
begin
while (not(Terminated)) do
begin
UpdateLocalConfigIfNecessary;
// Do stuff
end;
end;
如您所见,我有一个互斥锁来避免前面描述的那种竞争条件。 WaitForSingleObject(ConfigurationLocker, INFINITE); 当用户更改全局配置变量时调用:
procedure ChangeGlobalConfigVariables(const NewGlobalConfigA : String ; NewGlobalConfigB : Integer);
var
I : Integer;
begin
WaitForSingleObject(ConfigurationLocker, INFINITE);
GlobalConfigA := NewGlobalConfigA;
GlobalConfigB := NewGlobalConfigB;
for I := 0 to ThreadList.Count - 1 do
TTestThread(ThreadList[I]).UpdateLocalCopyOfConfigVariables := true;
ReleaseMutex(ConfigurationLocker);
end;
事实是,虽然它阻止了线程在更改全局配置变量的同时更新它们的本地配置变量副本,但它也阻止了两个线程同时更新它们的本地配置——甚至如果没有更改全局配置变量。据我所知,在进行写作时,竞争条件是一个问题。如果全局配置变量相同,那么所有线程都可以同时更新它们的本地副本而不会出现问题。
我说的对吗?如果是这样,有没有办法解决这个问题?当然,这不是一个大问题,但我仍然觉得必须有更好的解决方案...
无法仅在有写入权限时锁定代码。
但是,读取全局配置对象应该是非常快的。所以这不是瓶颈。
如何更快地处理这个?
1.使用 TCriticalSection 而不是 mutex
临界区比互斥体快得多。互斥量可用于多个进程,而临界区仅在当前进程中工作。您不需要这种昂贵的锁定。用这样的变量交换 ConfigurationLocker
ConfigurationCriticalSection: TCrticalSection
。您需要在启动时创建对象,因为它用作全局变量。
您以类似的方式使用临界区。示例:
procedure TTestThread.UpdateLocalConfigIfNecessary;
begin
ConfigurationCriticalSection.Enter;
try
if (UpdateLocalCopyOfConfigVariables) then
begin
LocalConfigA := GlobalConfigA;
LocalConfigB := GlobalConfigB;
UpdateLocalCopyOfConfigVariables := false;
end;
finally
ConfigurationCriticalSection.Leave;
end;
end;
try..finally..end
模式在这里非常重要。如果你不使用它,当在 Enter
和 Leave
之间引发异常时,你可能会陷入死锁。当您使用带 WaitForSingleObject
和 ReleaseMutex
的互斥锁时,您需要应用相同的模式。
2。将新值的副本发送到线程
不要让线程访问全局配置。 有许多不同的模式可以通知其他对象有关更改的信息。一种方法是调用提供值的方法。这可能看起来像这样:
TTestThread(ThreadList[I]).UpdateLocalCopyOfConfigVariables(GlobalConfigA, GlobalConfigB);
所以对 TTestThread(ThreadList[I]).UpdateLocalCopyOfConfigVariables := true
的调用将被替换。
线程对象将存储新值并在特定情况下应用它们。