比赛条件 - 互斥体可以更灵活吗?

比赛条件 - 互斥体可以更灵活吗?

问题描述:

我有一个TThread对象,名为TTestThread。当创建线程时,它们会创建全局配置变量的本地副本。用户可以随时更改全局配置变量,并且当他这样做时,所有线程都会通过其本地变量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; 

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

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

这似乎是一个很好的点TMultiReadExclusiveWriteSynchronizer工作

+0

只要注意'TMREWSync',它在某些情况下确实存在一些已知问题。如果您在Windows Vista +上运行,请考虑将Win32 [Slim Reader/Writer(SRW)Locks](https://msdn.microsoft.com/en-us/library/windows/desktop/aa904937.aspx)作为替代方案。 –

+0

我们在谈论什么样的问题?我做了一个快速搜索,但我不确定你指的是什么。 –

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

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

如何更快处理?

1.使用TCriticalSection而非互斥

一个关键部分是不是互斥体快得多。互斥锁可用于多个进程,而关键部分仅适用于当前进程。你不需要这种昂贵的锁定。由此类似的变量ConfigurationCriticalSection: TCrticalSection交换ConfigurationLocker。您需要在启动时创建对象,因为它用作全局变量。

您以类似的方式使用临界区。例如:

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将被替换。

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

+0

说有没有真正的,大规模的瓶颈时,你是对的。对于我来说,当他们实际上可以在没有问题的情况下实际读取全局配置变量时,让线程彼此等待,这让我觉得“奇怪”。虽然我很欣赏你的回答! –

+0

读写器锁定比互斥或关键部分更合适。这样的锁可以允许多个线程同时从配置中读取而无需彼此等待,同时在需要更改配置时仍然提供排它锁。 –