gpt4 book ai didi

multithreading - 是否有必要在Delphi中对 bool 属性进行多线程保护?

转载 作者:行者123 更新时间:2023-12-02 08:33:44 24 4
gpt4 key购买 nike

我找到了一个名为EventBus的Delphi库,我认为它将非常有用,因为Observer是我最喜欢的设计模式。

在学习其源代码的过程中,我发现了一段代码,可能是出于多线程安全性考虑,其原因如下(属性Active的getter和setter方法)。

TSubscription = class(TObject)
private
FActive: Boolean;
procedure SetActive(const Value: Boolean);
function GetActive: Boolean;
// ... other members
public
constructor Create(ASubscriber: TObject;
ASubscriberMethod: TSubscriberMethod);
destructor Destroy; override;
property Active: Boolean read GetActive write SetActive;
// ... other methods
end;

function TSubscription.GetActive: Boolean;
begin
TMonitor.Enter(self);
try
Result := FActive;
finally
TMonitor.exit(self);
end;
end;

procedure TSubscription.SetActive(const Value: Boolean);
begin
TMonitor.Enter(self);
try
FActive := Value;
finally
TMonitor.exit(self);
end;
end;

您能否告诉我 FActive的锁定保护是否必要,为什么?

最佳答案

概括

让我首先明确指出这一点:不要尝试将多线程开发提炼为一组“简单”规则。必须了解如何共享数据,以便评估哪种可用的并发保护技术将针对特定情况正确为

您提供的代码表明原始作者仅对多线程开发有一个肤浅的了解。因此,这是而不是要做的一课。

  • 首先,以这种方式锁定Boolean以进行读/写访问根本没有用。 IE。每个读取或写入已经是原子的。
  • 此外,在该属性确实需要保护并发访问的情况下:根本无法提供任何保护。

  • 最终结果是多余的无效代码,这些代码可以触发无意义的等待状态。

    线程安全

    为了评估“线程安全性”,应理解以下概念:
  • 如果有2个线程“争夺”访问共享内存位置的机会,则第一个线程将成为第一个线程,第二个线程将成为第二个线程。在没有其他因素的情况下,您无法控制哪个线程将首先“启动”其访问。
  • 您唯一的控制是如果“第一个”线程尚未完成其关键工作,则阻止“第二个”线程进行并发访问。
  • “关键”一词具有特殊的含义,可能需要花费一些精力才能完全理解。稍后记下有关为什么Boolean变量可能需要保护的解释。
  • 关键工作是指对共享数据进行的操作所需的所有处理均被视为已完成。
  • 与原子操作或事务完整性的概念有关。
  • 可以使“第二个”线程等待“第一个”线程完成,或者完全跳过其操作。
  • 请注意,如果两个线程同时访问共享内存,则根据每个线程处理的内部子步骤的确切顺序,可能会出现行为不一致的情况。

  • 这是考虑线程安全性时的基本风险和需要关注的领域。这是从中得出其他原理的基本原理。

    “简单”的读写(通常)是原子的

    并发操作不会干扰单个字节数据的读取/写入。您将始终获得完整的值,或者整体上替换该值。

    这个概念扩展到机器结构位大小的多个字节。但确实有一个警告,称为撕裂。
  • 如果内存地址未按位大小对齐,则字节有可能跨越一个对齐位置的末尾到下一个对齐位置的开始。
  • 这意味着在机器级别读取/写入字节可能需要进行2次操作。
  • 结果是2个并发线程可以交错其子步骤,从而导致读取无效/不正确的值。例如。
  • 假设一个线程在$ffff的现有值上写入$0000,而另一个线程在读取。
  • “有效”读取将返回$0000$ffff,具体取决于哪个线程是“第一个”。
  • 如果子步骤同时运行,则读取线程可能返回$ff00$00ff的无效值。
  • (请注意,某些平台在这种情况下仍可能保证原子性,但是我不具备对此进行详细评论的知识。)

  • 重申一下:单字节值(包括Boolean)不能跨越对齐的内存位置。因此,它们不受上述棘手问题的困扰。这就是为什么问题中试图保护Boolean的代码完全没有意义的原因。

    需要保护时

    尽管隔离读取和写入是原子的,但要注意的是,当读取一个值并影响写入决定时,则不能假定此 是线程安全的。最好通过一个简单的例子来说明。
  • 假设2个线程反转一个共享的 bool 值:FBool := not FBool;
  • 2个线程意味着这会发生两次,并且一旦两个线程都结束,则 bool 值应该以其初始值结束。但是,每个操作都是多步骤操作:
  • FBool读到线程本地的位置(堆栈或寄存器)。
  • 反转值。
  • 将取反的值写回到共享位置。
  • 如果没有使用线程安全机制,那么子步骤可以同时运行。两个线程都有可能:
  • 阅读FBool;两者都获得了初始值。
  • 两个线程都反转其本地副本。
  • 两个线程都将相同的取反值写入共享位置。
  • 最终结果是,当应将还原为其初始值时,该值将被反转。

  • 基本上,关键的工作显然不仅仅是简单地读取或写入值。为了在这种情况下正确 保护 bool 值,保护必须在 读取之前开始,并在 写入之后结束。

    要避免的重要一课是线程安全需要了解 数据如何共享。没有这样的理解,产生任意通用的安全机制 是不可行的。

    这就是为什么几乎可以肯定地肯定,问题中的EventBus代码中的任何此类尝试注定都是不足的(甚至是彻底的失败)。

    关于multithreading - 是否有必要在Delphi中对 bool 属性进行多线程保护?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49551019/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com