Emulate a TLS (Thread Local Storage) and a TP (Thread Pooling) feature
- Source: https://www.freebasic.net/wiki/wikka.php?wakka=ProPgEmulateTlsTp
- Last revised: 2025-01-14
How emulate a kind of TLS (Thread Local Storage) and a kind of TP (Thread Pooling) feature with FreeBASIC.
Preamble:
TLS (Thread Local Storage)
Static variables are normally shared across all the threads. If we modify a static variable, it is visible so modified to all the threads.
Unlike normal static variable, if we create a TLS static variable, every thread must have its own copy of the variable (but with the same access name), i.e. any change to the variable is local to the thread (locally stored).
This allows to create a thread-safe procedure, because each call to this procedure gets its own copy of the same declared static variables.
In normal procedure with static variables, the content of that variables can be updated by multiple threads, but with TLS, we can think of these as static data but local to each thread.
TLS data is similar to static data, but the only difference is that TLS data are unique to each thread.
TP (Thread Pooling)
A thread pool is a set of threads that can be used to run tasks based on user needs.
The thread pool is accessible via a Type structure.
Creating a new thread is an expensive act in terms of resources, both from a processor (CPU) point of view and from a memory point of view.
Also, in the event that a program requires the execution of many tasks, the creation and deletion of a thread for each of them would greatly penalize the performance of the application.
Therefore, it would be interesting to be able to share the creation of threads so that a thread that has finished executing a task is available for the execution of a future task.
1. How emulate a kind of TLS (Thread Local Storage) feature with FreeBASIC
The principle of this TLS emulation for FreeBASIC is to use a static array for each requested TLS variable, where each thread has its own unique index (hidden) to access the array element.
This unique index relating to the thread is deduced from the thread handle value:
With fbc version >= 1.08, the thread handle value is simply returned from the 'Threadself()' function calling (new function) from any thread.
With fbc version < 1.08, the code is more twisted:
The thread handle value is only accessible from the 'ThreadCreate()' return in the parent (or main) thread when creating it.
There is no way to properly emulate the 'Threadself()' function, but only by a twisted method.
In the example below (for fbc version < 1.08), a 'Threadself()' function (returning by reference) value is initialized before each use by the thread (with its own thread handle), and all of this (initialization + use) protected by a mutex as for its corresponding 'ThreadCreate()'.
In the below example, the TLS static variable is an integer which is used in a single and generic counting procedure ('counter()') with none passed parameter). This counting procedure is called by each thread (thus each thread counts independently of each other but by calling the same single counting procedure).
A single macro allows to define any TLS variable (except array) of any type.
- Code with preprocessor conditional directives depending on fbc version:
2. How emulate a kind of TP (Thread Pooling) feature with FreeBASIC
The objective of thread pooling is to pool the threads in order to avoid untimely creation or deletion of threads, and thus allow their reuse.
So when a task needs to be executed, it will be more resource efficient to check if the thread pool contains an available thread.
If so, it will be used while the task is running, and then freed when the task is completed.
If there is no thread available, a new thread can be created, and at the end of the task, the thread would be in turn available in the pool of threads.
Two Type structures are first proposed below:
These two structures make it possible to use one thread per instance created, and to chain on this dedicated thread the execution of user procedures one after the other, but without the thread stopping between each:
The 'ThreadInitThenMultiStart' structure requires a manual start after initialization (and manual wait for completion) for each user procedure to be executed in sequence in the thread.
The 'ThreadPooling' structure allows to register a sequence of user thread procedure submissions in a queue, while at same time the user procedures start to be executed in the thread without waiting (a last registered wait command is enough to test for full sequence completion).
By creating and using several instances, these two structures make it possible to execute sequences of user procedures in several threads, therefore executed in parallel (temporally).
A last structure is finally proposed:
This last structure is an over-structure of the ThreadPooling structure, dispatching user thread procedures over a given max number of secondary threads.
These 3 different structures are then compared from the point of view:
- ThreadInitThenMultiStart Type:
- ThreadPooling Type:
- ThreadDispatching Type, over-structure of ThreadPooling Type, dispatching user thread procedures over a given max number of secondary threads:
- Execution time gain checking with ThreadInitThenMultiStart, ThreadPooling, and ThreadDispatching Types:
- Time wasted when running a user task either by procedure calling method, by elementary threading method, or by various thread pooling methods:
See also
- Multi-Threading Overview
- Threads
- Mutual Exclusion
- Conditional Variables
- Critical Sections
- Critical Sections FAQ
Back to DocToc