【问题标题】:Is it safe to set the boolean value in thread from another one?从另一个线程中设置布尔值是否安全?
【发布时间】:2012-03-08 16:11:50
【问题描述】:

我想知道以下(伪)代码是否可以安全使用。我知道 Terminated 标志,但我需要在主线程的递归搜索操作中设置某种取消标志并保持工作线程运行。我还会在那里检查 Terminated 属性,这个伪代码中缺少什么。

type
  TMyThread = class(TThread)
  private
    FCancel: Boolean;
    procedure RecursiveSearch(const ItemID: Integer);
  protected
    procedure Execute; override;
  public
    procedure Cancel;
end;

procedure TMyThread.Cancel;
begin
  FCancel := True;
end;

procedure TMyThread.Execute;
begin
  RecursiveSearch(0);
end;

procedure TMyThread.RecursiveSearch(const ItemID: Integer);
begin
  if not FCancel then
    RecursiveSearch(ItemID);  
end;

procedure TMainForm.ButtonCancelClick(Sender: TObject);
begin
  MyThread.Cancel;
end;

以这种方式在线程内部设置布尔属性 FCancel 是否安全?当按下主窗体(主线程)中的按钮时,这不会与 RecursiveSearch 过程中读取此标志相冲突吗?或者我必须添加例如读取和写入此值的临界区?

非常感谢

【问题讨论】:

  • 如果我没记错的话,整数赋值是原子的,因此是线程安全的。
  • 看来你复制了Terminated 属性……不过应该是安全的。
  • 谢谢大家; Smasher 关于复制 Terminated 属性的评论是最好的解释。
  • @iamjoosy 仅在对齐时
  • @David 通常是 IIRC。不过,打包记录可能是个例外。

标签: multithreading delphi thread-safety


【解决方案1】:

这样做是绝对安全的。阅读线程将始终读取 true 或 false。不会有撕裂,因为Boolean 只是一个字节。事实上,对于 32 位进程中对齐的 32 位值也是如此,即Integer

这就是所谓的良性种族。布尔变量存在竞争条件,因为一个线程读取而另一个线程写入,没有同步。但是这个程序的逻辑并没有受到比赛的不利影响。在更复杂的情况下,这样的竞争可能是有害的,因此需要同步。

【讨论】:

    【解决方案2】:

    从不同的线程写入布尔字段是线程安全的——也就是说,写入操作是原子的。当值被写入该字段时,该字段的任何观察者都不会看到“部分值”。对于较大的数据类型,部分写入是可能的,因为需要多条 CPU 指令才能将值写入字段。

    因此,布尔值的实际写入不是线程安全问题。但是,观察者如何使用该布尔字段可能是线程安全问题。在您的示例中,唯一可见的观察者是 RecursiveSearch 函数,它对 FCancel 值的使用非常简单且无害。 FCancel 状态的观察者不会改变 FCancel 状态,所以这是一个直接/非循环的生产者-消费者类型依赖。

    如果代码使用布尔字段来确定是否需要执行一次性操作,那么对布尔字段进行简单的读写是不够的,因为布尔字段的观察者还需要修改该字段(标记一次性操作已完成)。这是一个读-修改-写循环,当两个或更多线程在正确的时间执行相同的步骤时,这是不安全的。在这种情况下,您可以在一次性操作(以及布尔字段检查和更新)周围放置一个互斥锁,或者您可以使用 InterlockedExchange 来更新和测试没有互斥锁的布尔字段。您还可以将一次性操作移到静态类型构造函数中,而不必自己维护任何锁(尽管 .NET 可能为此在幕后使用锁)。

    【讨论】:

    • 你的评论并没有让我渴望内置线程安全的静态初始化器............
    • “部分值”问题实际上取决于数据类型。 IIRC Intel 保证写入单个字节或正确对齐的 2 或 4 字节值将始终是原子写入。而且让编译器保证对齐并不难......
    • 没有.NET 是什么意思? .NET 是用 Delphi 编写的! :P
    【解决方案3】:

    我同意从一个线程写入布尔值并从另一个线程读取是线程安全的。但是,请注意 incrementing - 这不是原子的,并且可能会在您的代码中导致明显的非良性竞争条件,具体取决于实现。递增/递减通常会变成三个单独的机器指令 - 加载/递增/存储。

    这就是 InterlockedIncrement、InterlockedDecrement 和 InterlockedExchange Win32 API 调用的用途 - 使 32 位递增、递减和加载能够以原子方式发生,而无需单独的同步对象。

    【讨论】:

    • 只有一个线程在写通常没问题。您描述的问题仅适用于多个写入线程。
    【解决方案4】:

    是的,它是安全的,只有当您在另一个线程中读取或写入时才需要使用关键部分,在同一个线程中它是安全的。

    顺便说一句。你定义 RecursiveSearch 方法的方式,如果 (FCancel = False) 那么你会得到一个 Stack overflow (:

    【讨论】:

    • 非常时尚 :-) 但是关于临界区,实际上我正在将主线程(我从中执行 MyThread.Cancel)中的值写入属于工作线程的值,这就是我担心的。但正如 Smasher 所说,最好的例子是 Terminated 属性。还是谢谢!
    • 这不太正确。从一个线程写入对齐的整数并从另一个线程读取它是非常安全的。一个很好的例子是一个从 0 增加到 100 的进度计数器。虽然写入和读取线程在变量上竞争,但这种竞争是良性的,至少对于这个例子来说是这样。
    • @David 感谢您的纠正,我一直使用临界区,不知道在某些情况下没有它可能是安全的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-30
    • 2011-07-14
    相关资源
    最近更新 更多