Our latest Xojo update introduces a new thread type that maximizes the potential of your multi-core systems. This new preemptive thread model enhances efficiency and responsiveness, allowing for improved multitasking and resource management while delivering significant performance gains. In this post, we’ll explore the benefits of this upgrade, delve into the implementation details, and guide you on how to fully utilize the new preemptive threading capabilities.
Threading Models
Cooperative Threads: This is the threading model that Xojo has traditionally supported and continues to offer. In this model, threads must voluntarily yield control at specific points that we define. This ensures that most operations in Xojo code are thread-safe, as we manage access to MemoryBlocks, Arrays, etc. However, this approach can lead to inefficiencies if a thread fails to yield, potentially blocking UI, and because only one thread runs at a time, it doesn’t fully utilize multiple cores or truly run in parallel.
Preemptive Threads: Introduced in Xojo 2024r3, this new thread type allows threads to be interrupted at any point, enhancing responsiveness and fully utilizing multiple cores. While this model provides significant performance improvements, it also introduces new challenges. Because threads can be interrupted at any time, you’ll need to be more diligent about locking resources, such as MemoryBlocks and Arrays, to avoid potential race conditions.
Using Preemptive Threads
We’ve added a new Thread.Type property that allows you to switch your threads to the new preemptive thread model, here’s how you can start one up:
Thread1.Type = Threads.Types.Preemptive
Thread1.Start
You can set this property either before starting your thread or while the thread is running. However, if changing it while the thread is active, it must be done safely from within the running thread itself, and not externally.
Event Window1.Opening
// Start the thread and setup the type in the Run event instead
Thread1.Start
End Event
Sub Thread1.Run
Me.Type = Threads.Types.Preemptive
// Run a very long process preemptively.
newMemBlock = memBlock.Compress
Me.Type = Threads.Types.Cooperative
// Switching to cooperative mode and modifying a shared array safely.
mySharedArray.Add("New item")
End Sub
Concurrency and Synchronization
As mentioned earlier, cooperative threads avoid many of the issues that arise with preemptive threads, since we control when thread switching occurs. With preemptive threads, however, you can no longer safely modify things, like shared arrays, the same way you could before with cooperative threads. To manage this, you’ll need to use tools like CriticalSections
and Semaphores
more frequently to protect your shared resources.
The challenge lies in efficiently supporting both threading models while enabling the main thread to synchronize with either type. To address this, we’ve added a new Type property to CriticalSection
and Semaphores
, which must match the thread type you’re using to protect shared resources.
// Imagine the main thread and two additional threads all trying to access a Dictionary simultaneously.
Sub RunTest
Thread1.Type = Thread.Types.Preemptive
Thread2.Type = Thread.Types.Preemptive
CSLock = New CriticalSection
// Since we are using this CriticalSection in preemptive threads,
// the synchronization type must be compatible.
CSLock.Type = Thread1.Type
Thread1.Start
Thread2.Start
Do
CSLock.Enter
MyDictionary.RemoveAll
CSLock.Leave
Loop
End Sub
Sub Thread1.Run
Do
CSLock.Enter
MyDictionary.Value("Random1") = Rnd
CSLock.Leave
Loop
End Sub
Sub Thread2.Run
Do
CSLock.Enter
MyDictionary.Value("Random2") = Rnd
CSLock.Leave
Loop
End Sub
What things are thread-safe?
We’ve updated our framework to safeguard areas that were previously not preemptive thread-safe. For the most part, our entire framework is now safe for concurrent use with preemptive threads, with a few exceptions like XojoScript and Runtime.IterateObjects. When sharing resources such as MemoryBlocks, Dictionaries, or Arrays among threads, you may need to use CriticalSections
or Semaphores
to ensure safe access. However, if you’re only reading from these resources without modifying them, synchronization is not required. You’ll be happy to know that calling Thread.AddUserInterfaceUpdate
from a preemptive thread is also safe.
Performance Considerations
With the ability to switch your threads to preemptive, there are some performance factors to consider. While running 100 threads at once might seem appealing, it often leads to over-utilizing your system’s resources. For optimal efficiency, it’s best to limit the number of threads to match the number of available cores. To assist with this, we’ve introduced the new System.CoreCount, which helps you determine the number of cores on your system.
While Xojo’s framework doesn’t shield you from every potential issue, we do handle synchronization for key operations like object creation and destruction. However, you may notice these actions are slower in preemptive threads due to the extra synchronization required.
Usage and Examples
There’s no question that preemptive threads offer significant advantages, and when used correctly, they can greatly enhance your app’s performance. Check out our threading example projects included with the 2024r3 release to see preemptive threads in action and enjoy weaving these new threads into your future apps!
A special thanks to our MVPs for their invaluable help in refining and testing this new feature! Be sure to check out the new ThreadPool example for some practical, real-world use cases and read MVP Kem Tekinay’s blog post Preemptive Threads Are Here and They Are …. Pretty Good.
Need More Information? Read more about preemptive threads in the Xojo Documentation and Blog. Or read about Semaphore and CriticalSection.
William Yu grew up in Canada learning to program BASIC on a Vic-20. He is Xojo’s resident Windows and Linux engineer, among his many other skills. Some may say he has joined the dark side here in the USA, but he will always be a Canadian at heart.