A new mutlti-threading library for Delphi. (darkThreading)
My regular readers may be aware that over the past two or three months, I’ve been working on building a new video game engine for Delphi. I’ve also had many other demands on my time, which has meant that I’ve not posted in a while. Today however, I got to the point in the game development project that I’m able to release another of it’s sub-components as an open source library for Delphi. I’d like to introduce you to darkThreading.
Many multi-threading or parallel programming libraries adopt a task model. In such a model, the main thread prepares a number of tasks and throws them at a pool of threads to be executed. This model works very well in most cases, but there are some cases in which it is not ideal. The game engine project is such a case.
A video game engine has many components which run constantly, throughout the lifetime of the game process. For example, a typical game might have an audio component which is responsible for playing the chilling foot-steps sounds as a foe approaches you in the game. The problem for a task based threading model is that often the hardware components or drivers, such as the audio system, expect your application to acquire a context to use them, and those contexts are often restricted to a single thread. In a task based model, the game system would either need a separate audio context for each thread in the thread pool, or else it would need to acquire a new context each time foot steps are to be played. Grabbing and freeing contexts is not good for performance, and so a better model is needed.
The darkThreading library solves this problem by providing a number of long-running threads, each of which may be dedicated to a particular function, such as playing creepy footstep sounds. During the periods that the threads aren’t needed, they can be ‘put-to-sleep’ in such a way that they aren’t consuming CPU cycles, and those cycles may be given over to another system which needs them. The darkThreading library retains the thread, and therefore any associated hardware contexts, and the thread may then simply be woken when it is needed.
darkThreading in more detail.
Like most multi-threading libraries, darkThreading provides a few ‘primitives’, that is classes for working with concurrent execution, which you can use to make your code thread safe.
The Critical Section uses a mutex (mutually exclusive) lock to ensure that a particular piece of code may only be executed by a single thread at any time.
A Signaled Critical Section performs the same task as a Critical Section, but after the lock has been aquired, the exclusive running code may put the executing thread to sleep. While this thread is asleep it is removed from the operating system scheduling and therefore not consuming CPU cycles. Any other thread may then call on the ‘Wake’ method to wake the executing thread back up.
An atomic ring buffer uses an atomic variable (a variable that may only be written to by a single thread at a time) to provide a buffer which can safely copy data from one running thread to another. The ring buffer is uni-directional, in that only one thread may copy data in, and only one (other) thread may copy data out of the buffer at any time.
A thread method is a class which repeatedly calls a specified method from within a thread. This primitive is the basis of all threads in the darkThreading library.
darkThreading also provides a thread pool system for starting up multiple threads.
A pool thread is a class that will be executed by a single dedicated thread within a pool of threads.
A thread pool is a collection of IPoolThreads, each of which will be executed in it’s own dedicated thread.
Where darkThreading differs from many threading libraries is in it’s more specialized classes and types…
Is a record representing a message (not unlike the windows API messages) that may be transmitted between sub-systems (see IThreadSubSystem) running on different threads.
A message channel is owned by a single thread, and is used by that thread to listen for messages from other threads. Other threads are able to send messages into the channel through the use of message pipes. Any one channel may have multiple message pipes, making the channel a multi-sender single-receiver messaging system.
A message pipe is owned by a single thread and used to send messages into a message channel which is owned by another thread. Multiple threads may each own their own pipe to the target channel.
The message bus is simply an aggregating interface to combine the functionality of IMessagePipe and IMessageChannel instances. The message bus interface provides access to the methods of each of the other interfaces.
A thread sub system is a slightly more advanced form of IPoolThread. It is a long running task which may be installed into an IThreadSystem to be executed. Unlike IPoolThread the IThreadSubSystem is able to be run in round-robin alongside other sub systems on the same thread. When sharing a thread, the IThreadSubSystem is expected to be cooperative and yield it’s free run-time to other subsystems. IThreadSubSystem also has access to the messaging system, allowing subsystems to communicate using the messaging interfaces.
A thread system is a more advanced form of thread pool (IThreadPool). It is a collection of IThreadSubSystem instances which run on a pre-determined number of threads. IThreadSystem provides its sub systems with access to the messaging system.
How to use it?
The documentation for darkThreading is still in need of a little work (which will happen soon!), but it does come with several sample applications to demonstrate it’s features. For the moment, if you’re not sure how to make use of the library, please be patient for documentation updates and a follow up blog post with more details.
How to get it?
That’s the best bit. Like all of the components of the darkGlass game engine, darkThreading is free to use as you see fit under the MIT license. You can clone your own copy right from GitHub: https://github.com/chapmanworld/darkThreading
Watch out for my next post on darkThreading. In which I’ll write a sample application with you, and give updates on the documentation changes.
Meanwhile, feel free to experiment and get in touch if you have questions.
Thanks for reading!