The example code for using TLS appear to be incomplete, with no indication how each thread is to be initialized. Presumably an init function is required that loops through all threads (assuming all possible threads have already been created) and provides the exact same static or global variable pointer to each. Since the pointer is the same, then how does storing a copy of this address in each task avoid access conflicts? Even worse, the example for “Thread Local Structures” makes no sense: at line 10, pxStruct is assigned a value via malloc, after which values are assigned to the structure members (but with no NULL pointer check!!) and then the pointer is stored in the task at line 18. Then at line 24 the pxStruct value is retrieved - WHY?? It was just assigned at line 10, so why do we need to read it back from the thread at line 24? There is insufficient information in your TLS page to implement this system.
Hello @KevinD, thanks for contacting the forum.
It looks like you are talking about the sample code here.
Thread Local Storage Pointers (TLS) is used to give a task a variable ( either numeric or a pointer ), that is accessible only by the “owning” task.
I can imagine that the examples are confusing when you’re not used to it: the code shown is just a simplified example. That is why the results of functions are not tested.
Also in mentioned text, it is assumed that you’ve gone through the basics of FreeRTOS: create tasks and run an application.
I used TLS in a FAT driver: both the current working directory (CWD) and the last error (which is like errno) are task-local variables.
See an example here
The aim of TLS is to enable the application writer to store and retrieve value as needed:
Thread local storage (or TLS) allows the application writer to store values inside a task's control block, making the value specific to (local to) the task itself, and allowing each task to have its own unique value.
Therefore, there is no attempt to initialize.
As @htibosch mentioned, the example code here is an overly simplified for readability.
Thanks @htibosch. I believe I wasn’t sufficiently clear in describing my issues. I have in fact been using FreeRTOS for a while, including creating multiple tasks and running applications. My concern is that function vTaskSetThreadLocalStoragePointer() simply stores its input pointer arg in the appropriate TCB’s pvThreadLocalStoragePointers array. If the address of the global or static resource being accessed by multiple threads is 0x12345678, then each thread’s TCB pointer array will contain 0x12345678. However, there seems to be an implicit understanding that somehow this address is modified in some way such that the referenced object is protected from access collisions. I don’t see how this works, since the FreeRTOS source code does not show any address translation or even copying of the shared resource to the task’s local storage. All threads are pointing to 0x12345678, and collisions will occur. What am I overlooking?
Thanks @aggarg; as I mentioned in my response to @htibosch, my problem description wasn’t very clear. I understand the aim of TLS but not the implementation as revealed by the FreeRTOS task source code. What I meant by initialize is that shortly after each thread is created, it must call vTaskSetThreadLocalStoragePointer() in an attempt to preserve a thread-local copy of the shared resource; that is, initialize the TCB’s pvThreadLocalStoragePointers array. I can appreciate that the example code is overly simplified, but nonetheless there is nothing in that code or the FreeRTOS source code that shows how a copy of a shared resource is saved to a specific thread. Saving a copy of the resource address means all threads will store the same address and then try to access the same object, which is precisely what we wish to avoid. What am I missing here?
Each task has its own copy of pvThreadLocalStoragePointers array which can hold configNUM_THREAD_LOCAL_STORAGE_POINTERS pointers. Storing a pointer in one task’s pvThreadLocalStoragePointers array does not make it magically appear in other task’s pvThreadLocalStoragePointers array. May be the following example can help:
void Task1( void * param )
{
void * val;
for( ;; )
{
/* Set 0x12345678 at index 0 in the pvThreadLocalStoragePointers
* array of task 1. */
vTaskSetThreadLocalStoragePointer( NULL,
0,
( void * ) 0x12345678 );
/* Get the value at index 0 in the pvThreadLocalStoragePointers
* array of task 1. val will be 0x12345678. */
val = pvTaskGetThreadLocalStoragePointer( NULL,
0 );
}
}
void Task2( void * param )
{
void * val;
for( ;; )
{
/* Set 0xABCD1234 at index 0 in the pvThreadLocalStoragePointers
* array of task 2. */
vTaskSetThreadLocalStoragePointer( NULL,
0,
( void * ) 0xABCD1234 );
/* Get the value at index 0 in the pvThreadLocalStoragePointers
* array of task 2. val will be 0xABCD1234. */
val = pvTaskGetThreadLocalStoragePointer( NULL,
0 );
}
}
If you share an object among tasks, then you need to ensure thread safety.
It should be noted that TLS is a pretty awkward hybrid between static and automatic variables and at the end of the day a crutch.
The reason why it exists in the first place is its poster case errno - in the days before multithreading a global variable, but when multithreading came into play, all non-reentrant usages of errno in existing code (eg libraries) ran into concurreny troubles. Note that relocating errno into TLS opens up another Pandora Box of troubles, eg whether and if how to resync the thread specific instances at join time. At the end of the day, TLS appears to be a workaround for C failing to be able to support dynamic vs. lexical scoping.
For new developments not involving legacy code, I can not think of a single use case for TLS. If you need reentrant code, use local variables. If you need to access shared data, use static/global storage and mutual exclusion.
I’d be intersted in seeing a real use case for TLS for new developments. I came across a sketch here: Thread local storage for “advanced users”? - Kernel - FreeRTOS Community Forums , but I believe there are better ways to solve that issue - TLS will somewhat work but at a cost. In that use case, I’d prefer to pass a reference to the watchdog counter value instance down to the share code; if for no other reason, then for referential transparency which becomes completly outlevered with TLS.
Another thought here: The ugly small print about TLS becomes more obvious when we look at something that is very Unix/Linux: Parent/child relationships between threads. Assume thread A spawns thread B at run time. In Unix/Linux, this implies among other things a parental relationship between the two, which in turn implies that each instance of a TLS variable in B is initialized from A at creation time. That requires run time support; furthermore, in OSs that do not maintain parental relationships (eg FreeRTOS), the initialization is undefined - and because there is no join as in Unix/Linux, no semantics can be defined or implemented when the spawned thread terminates.
Thanks, your last statement is exactly what I was looking for. I was hoping that TLS would promote thread safety, but clearly it can’t be used for shared resources (global/static scope) - I still must provide independent copies of each such resource within each thread’s stack, and likely a pointer input/output argument to support reentrancy.
Excellent response! As I mentioned in my response to @aggarg, I was hoping for thread safety via TLS, but now it’s clear I’m still going to need copies of shared resources in each thread’s stack. I agree with you - what’s a real use case for TLS for new developments? Anyway, I’m clear on how to proceed. Thanks!
Another excellent point, thanks again!