Thread Safety and Atomic Scalars

I’m maintaining a very mature app that is starting to get a little busy and timing-sensitive. As I move mundane tasks to threads to free up CPU time, I’m beginning to wonder how atomic certain Xojo properties are for passing simple state around — Boolean values and Integers mostly — and whether they’re safer on the Thread object itself or if it doesn’t matter.

Assuming I don’t care if we have a race and I pull a stale value in one pass (I’ll get the new value on the next), should I be worried about crashing the app if I try to read and write a scalar at the same time?

Right now I’m wrapping every access in CriticalSections, which works fine but feels like possibly overkill for things like reading a single Boolean property.

Sure enough, this isn’t really addressed in the docs…

Are reads/writes to simple types (Boolean, Integer) atomic in Xojo, or can a thread see a partially-written value?

Does Xojo’s new preemptive threading model change the calculus here? Assuming cooperative threads yield at specific points rather than being preempted mid-instruction, is there an implicit safety guarantee for simple assignments? Does this change in preemptive mode?

For those of you building thread-heavy applications — where do you draw the line between “this needs a CriticalSection” and “a simple property is fine”? Does that line even exist?

Intel or ARM CPUs?

Because things are a bit different on the lower levels. And then it depends on how well the compiler optimizes. Xojo loves to put intermediate results in temporary stack variables and you never know if you get a different thread access a property before/after.

With cooperative threads, you don’t need to care. Only one piece of code runs at a time and the thread switches happen at the end of loops.

You could use MBS Xojo Plugins and the classes inside:

These are itself thread safe. Not by using expensive locks like CriticalSection, but by using instructions in the CPU to do synchronized reads and writes.

1 Like

I’ve asked these sorts of questions about preemptive threads in the past and never really got a detailed answer. Cooperative threads barely need any concurrency management. So for preemptive threads I lock every access of any property that could be used by multiple threads. The trouble is, logically the CriticalSection itself has to reside inside a property, so Xojo must be doing some kind of thread safety.

This leads me to suspect that property access itself is safe. Read and write to your heart’s content. It’s the stuff inside that needs to be taken care of. Anything that requires multiple lines of code be guaranteed to be executed without interruption.

A good example would be reading a value from a Dictionary. Calling Dictionary.Value is fine, but something like If Dict.HasKey(Key) Then Dict.Remove(Key) is trouble because it wouldn’t be impossible for the key to have already been removed by another thread. This is where you’d need to lock so that the two actions are performed without interruption.

Certain code structures are a problem with this though. Consider a queue:

While StringArray.Count > 0
  Var Member As String = StringArray(0)
  StringArray.RemoveAt(0)

  // Do things
Wend

You can’t just lock the entire loop, as that would make the queue useless. This is where AtomicQueueMBS really shines. But if you don’t want to use that, there are ways:

Lock.Enter
While StringArray.Count > 0
  Var Member As String = StringArray(0)
  StringArray.RemoveAt(0)
  Lock.Leave

  // Do things

  Lock.Enter
Wend
Lock.Leave

This way you’re locking the boundary and the initial access together, while allowing manipulation of the array by other threads.

Exceptions will be your mortal enemy here:

Lock.Enter
Raise New NilObjectException
Lock.Leave

Your lock will be left open. This is just an example of course, the exception could come from anywhere. So I recommend always including a try block an having a plan for your exceptions:

Lock.Enter
Try
  Raise New NilObjectException
Catch Err As RuntimeException
  // Ignoring it is an option. Not a great one, but an option.
End Try
Lock.Leave

This is starting to look like a TON of boilerplate. So I’ve got one last tip for you: destructors.

Build a simple class that accepts a CriticalSection, locks it in the constructor, and unlocks it in the destructor. When the object goes out of scope for any reason, the lock is released.

Var Holder As New LockHolder(Lock)
Raise New NilObjectException

The lock still gets released because the variable will go out of scope.

Ken Tekinay has a great example of this in Xojo-ThreadPool/Common/M_ThreadPool/LockHolder.xojo_code at master · ktekinay/Xojo-ThreadPool · GitHub which is where I got the idea from.

And prepare to lose some hair. Preemptive threads are a nightmare. I’ve got an article at The ZAZ: Navigating the Perils of Preemptive Threads and Locking that repeats a lot of this, but has some additional tips and traps to be aware of.

6 Likes