Delphi的简单类型是否安全?

Leo*_*Leo 12 delphi multithreading thread-safety

我声明了两个全局变量:

var
  gIsRunning: Boolean = False;
  gLogCounter: Integer = 0;
Run Code Online (Sandbox Code Playgroud)

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

klu*_*udg 49

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

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

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

  • 我在这个问题上看到的最佳总结评论! (6认同)
  • 除了一件事,我同意你的回答.*Atomicity*是*operation*的属性,不是*variable*或*data type*.你可以说,例如增量是原子的,但你不能说整数是原子的.你可以在这里查看:http://en.wikipedia.org/wiki/Atomicity_ (programming) (2认同)

Mas*_*ler 13

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

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

  • 没有@Rruz,全局变量不被认为是良好的设计实践.线程与此无关. (2认同)
  • @Rob,我不是在谈论全局变量.我只想指出如果OP实现一个系统(应用程序),基于全局变量和多个线程而不使用关键部分,而某一天另一个程序员或他自己在一个线程内写一个全局变量,应用程序将变得不稳定. (2认同)
  • @mghie我指的是Mason在他的回答中提到的上下文切换.你不需要上下文切换就可以防止撕裂.你只需要两个共享内存总线的线程. (2认同)

Ken*_*ssa 11

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

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

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

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

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

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

例如,

var
  LastGoodValueTested : Integer

procedure TestValue(aiValue : Integer);
begin
  if ValueGood(aiValue) then
    LastGoodValue := aiValue
end;
Run Code Online (Sandbox Code Playgroud)

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

现在,

var     
  gLogCounter: Integer = 0;

procedure Log(S : string);
begin
  gLogCounter := gLogCounter + 1;
end;
Run Code Online (Sandbox Code Playgroud)

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

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

procedure Log(S : string);
begin
  InterlockedIncrement(gLogCounter);
end;
Run Code Online (Sandbox Code Playgroud)

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


RRU*_*RUZ 8

不,他们不会是线程安全的,必须使用例如一个关键部分,使用访问这些变量InitializeCriticalSection,EnterCriticalSection和LeaveCriticalSection函数

//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); 
Run Code Online (Sandbox Code Playgroud)

  • @Rob这个问题并未在我看来为任何人指定足够的细节.你已经假设了你认为的问题,但现实是OP没有提出一个结构良好的问题.OP没有说明如何访问这些变量.RRUZ做了一个假设.其他人,包括你自己,做出了不同的假设.所有答案都做出了合理的假设,但假设在软件开发中无处可寻. (3认同)
  • @Rob没有锁定可能出错的是读写顺序. (2认同)