从另一个线程中设置线程中的布尔值是否安全?

时间:2012-03-08 16:11:51

标签: multithreading delphi thread-safety

我想知道以下(伪)代码是否可以安全使用。我知道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过程中读取此标志相冲突吗?或者我必须添加例如读取和写入这个值的关键部分?

非常感谢

4 个答案:

答案 0 :(得分:17)

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

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

答案 1 :(得分:8)

从不同的线程写入布尔字段是线程安全的 - 这意味着,写操作是原子的。当该值被写入该字段时,该字段的观察者将不会看到“部分值”。对于较大的数据类型,部分写入是可能的,因为需要多个CPU指令将值写入字段。

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

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

答案 2 :(得分:4)

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

这就是InterlockedIncrement,InterlockedDecrement和InterlockedExchange Win32 API调用的用途 - 使32位递增,递减和加载能够在没有单独同步对象的情况下以原子方式发生。

答案 3 :(得分:2)

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

顺便说一句。您定义RecursiveSearch方法的方式,如果(FCancel = False)那么您将获得堆栈溢出(: