竞争条件 - 互斥体可以更灵活吗?

时间:2015-11-10 14:08:56

标签: multithreading delphi mutex delphi-2007

我有一个名为 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;

问题是,虽然它可以防止线程在更改全局配置变量的同时更新其配置变量的本地副本,但它也可以防止两个线程同时更新其本地配置 - 即使全局配置变量未被更改。据我所知,在进行写作时,竞争条件是一个问题。如果全局配置变量相同,则线程可以同时更新其本地副本而不会出现任何问题。

我是对的吗?如果是这样,有没有办法解决这个问题?当然,它不是一个大的,但我仍然觉得必须有一个更好的解决方案......

2 个答案:

答案 0 :(得分:5)

TMultiReadExclusiveWriteSynchronizer工作似乎是一个好点

答案 1 :(得分:4)

只有在有写访问权限时才能锁定代码。 MBo's answer显示了如何在写入被锁定时允许多个线程读取变量的方法。

但是,读取全局配置对象应该非常快。所以这不能成为瓶颈。

如何更快地处理?

<强> 1。使用TCriticalSection而不是互斥

关键部分比互斥锁快得多。互斥锁可用于多个进程,而关键部分仅适用于当前进程。你不需要这种昂贵的锁定方式。通过此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模式在这里非常重要。如果您不使用它,则在EnterLeave之间引发异常时,您可能会死锁。使用WaitForSingleObjectReleaseMutex的互斥锁时,需要应用相同的模式。

<强> 2。将新值的副本发送到线程

不要让线程访问全局配置。 有许多不同的模式可以告知其他对象有关变化的信息。一种方法是调用提供值的方法。这看起来像这样:

TTestThread(ThreadList[I]).UpdateLocalCopyOfConfigVariables(GlobalConfigA, GlobalConfigB);

所以对TTestThread(ThreadList[I]).UpdateLocalCopyOfConfigVariables := true的调用将被替换。

线程对象将存储新值并将其应用于特定情况。