Delphi的简单类型是否安全?

时间:2011-03-30 01:51:28

标签: multithreading delphi thread-safety

我宣布了两个全局变量:

var
  gIsRunning: Boolean = False;
  gLogCounter: Integer = 0;

这些变量只写在主线程中,并在其他线程中读取。在这种情况下,这些变量是否安全?

4 个答案:

答案 0 :(得分:49)

你可能在谈论原子变量。整数和布尔变量都是原子的。布尔值(字节)总是原子的,整数(32位)是原子的,因为编译器正确地对齐它们。

原子性意味着任何读或写操作都是作为一个整体执行的。如果线程A同时执行原子写入和线程B原子读取相同数据,则线程B读取的数据始终是一致的 - 线程B读取的某些位不可能从当前写入操作获得并且前一次写入的一些位(通过线程A)

但原子性并不意味着线程安全 - 您可以使用原子变量轻松编写不安全的代码。变量本身不能是线程安全的 - 只有代码作为一个整体可以(或不是)线程安全。

答案 1 :(得分:13)

只要只有一个线程可以写入它们,那么是的,它们是线程安全的。线程安全的真正问题是两个线程试图同时修改一个值,你不会在这里。

如果它们更大,如记录或数组,则可能会遇到一个线程尝试写入值,中途获取,然后获取上下文切换以及另一个线程读取部分(因此损坏)数据的问题。但是对于单独的布尔值(1字节)和整数(4字节)值,编译器可以自动对齐它们,以便CPU可以保证对它们的所有读取和写入都是原子的,因此这不是问题。

答案 2 :(得分:11)

简单类型是“线程安全的”,只要它们可以在内存中以单次读取(或单次写入)读取即可。我不确定它是由CPU内存总线宽度还是它们的“整数”大小(32位对64位cpu)定义的。也许其他人可以澄清这一部分。

我知道现在的读取大小至少为32位。 (回到英特尔286天,它一次只有8位)。

虽然有1件事要知道。尽管它一次可以读取32位,但它无法在任何地址开始读取。它需要是32位(或4字节)的倍数。因此,如果它没有与32位对齐,即使整数也可以在2次后续读取中读取。值得庆幸的是,编译器会自动将所有字段与32位(甚至64位)对齐。

但是有一个例外,打包记录永远不会对齐,因此,即使这样的记录中的整数也不是线程安全的。

由于它们的大小,int64也不是线程安全的。关于大多数浮动类型也可以这样说。 (我相信单身除外)。

现在,考虑到所有这些,在某些情况下,您可以从多个线程实际编写一个全局变量,并且仍然是“线程安全的”。

例如,

var
  LastGoodValueTested : Integer

procedure TestValue(aiValue : Integer);
begin
  if ValueGood(aiValue) then
    LastGoodValue := aiValue
end;

这里,你可以从多个线程调用例程TestValue,你不会损坏 LastGoodValueTested变量。可能会发生写入变量的值不是最后一个。 (如果在ValueGood(aiValue)和赋值之间发生线程上下文切换)。因此,根据需要,它可能/可能不被接受。

现在,

var     
  gLogCounter: Integer = 0;

procedure Log(S : string);
begin
  gLogCounter := gLogCounter + 1;
end;

在这里,您实际上可以损坏计数器,因为它不是一元操作。你首先阅读变量。然后添加1。然后你把它保存回来。线程上下文切换可以在这些操作的中间发生。所以这是一个需要同步的情况。

在这种情况下,可以将其重写为

procedure Log(S : string);
begin
  InterlockedIncrement(gLogCounter);
end;

我认为这比使用关键部分要快一些......但我不确定。

答案 3 :(得分:8)

不是它们不是线程安全的,您必须使用InitializeCriticalSectionEnterCriticalSectionLeaveCriticalSection函数来使用例如关键部分来访问此类变量/ p>

//declaration of your global variables 
var 
   MyCriticalSection: TRTLCriticalSection;
   gIsRunning: Boolean;
   gLogCounter: Integer;

//before the threads starts
InitializeCriticalSection(MyCriticalSection); 

//Now in your thread 
  EnterCriticalSection(MyCriticalSection); 
//Here you can make changes to your variables. 
  gIsRunning:=True;
  inc(gLogCounter); 
//End of protected block
LeaveCriticalSection(MyCriticalSection);