Skip to content

Cooperative to Preemptive: Weaving New Threads into your Apps

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.