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?
To test if the combination of two methods, a and b, is thread-safe, call them from two different threads. Put the complete test in a while loop iterating over all thread interleavings with the help from the class AllInterleavings from vmlens. Test if the result is either an after b or b after a.
Global variables are still not thread safe because there's still no protection against most race conditions. You can still have a scenario where one worker gets a value, yields, another modifies it, yields, then the first worker also modifies it.
To use a thread object in your application (and to create a descendant of Classes. TThread): Choose one: File > New > Other > Delphi Projects > Delphi Files > Thread Object.
atomic which offers lock-free and thread-safe classes. Variables of these class types are called as atomic variables. There are 12 classes within this package.
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). Maybe someone else can clarify that part.
I know the read size nowaday is at least 32 bits. (Back in the Intel 286 days, it was only 8 bits at a time).
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. It needs to be a multiple of 32 bits (or 4 bytes). So, even an integer could be read in 2 subsequent reads if it's not aligned to 32 bits. Thankfully, the compiler will align pretty much all fields to 32 bits (or even 64 bits) automatically.
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. 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. 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). 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. 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With