简体   繁体   English

Delphi的简单类型是否安全?

[英]Are Delphi simple types thread safe?

I declared two global variables: 我声明了两个全局变量:

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

These variables are written only in the main thread, and read in other threads. 这些变量仅在主线程中写入,并在其他线程中读取。 In this case, are these variables thread safe? 在这种情况下,这些变量是否安全?

You are probably saying about atomic variables. 你可能在谈论原子变量。 Integer and Boolean variables are atomic. 整数和布尔变量都是原子的。 Booleans (bytes) are always atomic, integers (32-bits) are atomic because the compiler properly aligns them. 布尔值(字节)总是原子的,整数(32位)是原子的,因为编译器正确地对齐它们。

Atomicity means that any read or write operation is executed as a whole. 原子性意味着任何读或写操作都作为一个整体执行。 If a thread A executes atomic write and a thread B atomic read of the same data at the same time, the data read by thread B is always consistent - it is impossible that some bits read by thread B are obtained from the current write operation and some bits from the previous write (by thread A) 如果线程A同时执行原子写入和线程B原子读取相同数据,则线程B读取的数据始终是一致的 - 线程B读取的某些位不可能从当前写入操作获得并且上一次写入的一些位(由线程A)

But atomicity does not mean thread safety - you can easily write unsafe code with atomic variables. 但原子性并不意味着线程安全 - 您可以使用原子变量轻松编写不安全的代码。 A variable itself cannot be threadsafe - only a code as a whole can be (or not) threadsafe. 变量本身不能是线程安全的 - 只有代码作为一个整体可以(或不是)线程安全。

As long as there is only one thread that can write to them, then yes, they're thread safe. 只要只有一个线程可以写入它们,那么是的,它们是线程安全的。 The real problem with thread safety is two threads trying to modify a value at the same time, and you won't have that here. 线程安全的真正问题是两个线程试图同时修改一个值,你不会在这里。

If they were larger, like records or arrays, you might have issues with one thread trying to write a value, getting partway through, then getting context-switched and another thread reads partial (and therefore corrupt) data. 如果它们更大,比如记录或数组,则可能会遇到一个线程尝试写入值,中途获取,然后获取上下文切换以及另一个线程读取部分(因此损坏)数据的问题。 But for individual boolean (1 byte) and integer (4 byte) values, the compiler can automatically align them in such a way that the CPU can guarantee that all reads and writes to them are atomic, so that isn't a problem here. 但是对于单独的布尔值(1字节)和整数(4字节)值,编译器可以自动对齐它们,以便CPU可以保证对它们的所有读取和写入都是原子的,因此这不是问题。

Simple types are "thread-safe" as long as they can be read in a single read (or written in a single write) from the memory. 简单类型是“线程安全的”,只要它们可以从存储器中单次读取(或以单次写入方式)读取。 I'm not sure if it's defined by the CPU memory bus width, or their "integer" size (32 bits vs 64 bits cpu). 我不确定它是由CPU内存总线宽度还是它们的“整数”大小(32位对64位cpu)定义的。 Maybe someone else can clarify that part. 也许其他人可以澄清那一部分。

I know the read size nowaday is at least 32 bits. 我知道现在的读取大小至少是32位。 (Back in the Intel 286 days, it was only 8 bits at a time). (回到英特尔286天,它一次只有8位)。

There is 1 thing to know about this though. 但是有一件事要知道。 Even though it can read 32 bits at a time, it cannot start a read at just any address. 尽管它一次可以读取32位,但它无法在任何地址开始读取。 It needs to be a multiple of 32 bits (or 4 bytes). 它需要是32位(或4字节)的倍数。 So, even an integer could be read in 2 subsequent reads if it's not aligned to 32 bits. 因此,如果它没有与32位对齐,即使整数也可以在2次后续读取中读取。 Thankfully, the compiler will align pretty much all fields to 32 bits (or even 64 bits) automatically. 值得庆幸的是,编译器会自动将所有字段与32位(甚至64位)对齐。

There is an exception to this though, packed records are never aligned, and thus, even an integer in such a record wouldn't be thread safe. 但是有一个例外,打包记录永远不会对齐,因此,即使是这样的记录中的整数也不是线程安全的。

Because of their size, int64 are not thread safe either. 由于它们的大小,int64也不是线程安全的。 The same can be told about most floating types. 关于大多数浮动类型也可以这样说。 (Except Single I believe). (单身,我相信)。

Now, with all that in mind, there is some situation where you could actually write a global variable from multiple thread and still be "thread-safe". 现在,考虑到所有这些,在某种情况下,您可以从多个线程实际编写一个全局变量,并且仍然是“线程安全的”。

For example, 例如,

var
  LastGoodValueTested : Integer

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

here, you could call the routine TestValue from multiple threads and you wouldn't corrupt the LastGoodValueTested variables. 在这里,你可以从多个线程调用例程TestValue,你不会破坏 LastGoodValueTested变量。 It could happen that value that is written to the variable wouldn't be the very, very last though. 可能会发生写入变量的值不是最后一个。 (If a thread context switch happen between ValueGood(aiValue) and the assignation). (如果在ValueGood(aiValue)和赋值之间发生线程上下文切换)。 So, depending on the needs, it may/may not be acceptable. 因此,根据需要,它可能/可能不被接受。

Now, 现在,

var     
  gLogCounter: Integer = 0;

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

Here, you can actually corrupt the counter because it's not a unary operation. 在这里,你实际上可以破坏计数器,因为它不是一元操作。 You first read the variable. 你首先阅读变量。 Then add 1 to it. 然后添加1。 Then you save it back. 然后你把它保存回来。 A thread context switch can happen in the middle of those operation. 线程上下文切换可以在这些操作的中间发生。 So that is a case that requires synchronization. 因此,这是需要同步的情况。

In that case, it could be rewritten to 在这种情况下,它可以重写为

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

I would think this is slightly faster than using critical sections... But I'm not sure. 我认为这比使用关键部分要快一些......但我不确定。

No they are not are thread safe , you must access such variables using for example a critical section, using InitializeCriticalSection , EnterCriticalSection and LeaveCriticalSection functions 不,它们不是线程安全的 ,您必须使用例如临界区访问这些变量,使用InitializeCriticalSectionEnterCriticalSectionLeaveCriticalSection函数

//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); 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM