Thread-local Storage (TLS)
2011-10-11 09:59:28| Category: Win32---API | Tags: TLS | report | Font Subscription
What is thread-local storage
As we all know, a thread is a unit of execution, and multiple threads in the same process share the address space of the process, and the thread generally has its own stack, but if you want to implement a global variable that takes a different value between different threads and is unaffected. One way is to use the thread synchronization mechanism, such as the reading and writing of this variable in the critical area or mutex, but this is at the expense of efficiency, can not lock it? Thread-local Storage (TLS) is doing this.
Although TLS is convenient, it is not completely unlimited. Among Windows NT and Windows 95, there are 64 DWORD slots for each thread to use. This means that a process can have up to 64 dwords that "have different meanings for each thread". Although TLS can hold a single value such as file handle, the more common use is to place pointers, pointing to the private data of the thread. In many cases, multi-threaded routines need to store a bunch of data, and they are all related to each thread. Many programmers do this by wrapping these variables as C structures and then storing the structure pointers in TLS. When a new thread is born, the program configures some memory for the structure to use, and stores the pointer in the TLS reserved for the thread. Once the thread ends, the program code frees all configured chunks. Since each thread has 64 slots to store the thread's own data, where does the space come from? In the thread learning we can see from the structure TDB, each thread database has 64 DWORDs for TLS use.
Each thread has its own private resource in addition to the resources of the shared process: a register group (or thread context), a proprietary stack, a proprietary message queue, an exclusive thread Local Storage (TLS), and a proprietary structured exception handling chain. The system records all relevant data for the execution thread in a specific data structure (thread database,tdb), including thread local storage (thread local STORAGE,TLS), Message Queuing, handle table, address space (Memory Context) and so on.
When you set or remove data with TLS, the fact that you are really facing that is the DWORDs. Well, now we know that those "global variables that have different meanings to each thread" are the tdb of the respective threads in the online process. Next you may ask: how do I access these 64 DWORDs? How do I know which DWORDs is occupied, which is not occupied? First of all, we must understand the fact that the system provides us with TLS as a convenient way to implement the "global variables that have different meanings to each thread", and since we want to achieve the effect of "global variables", that is to say, each thread needs to use this variable. In that case, we don't need to mark the 64 DWORDs of each thread, because one of the 64 DWORDs is occupied and the DWORD of all the threads is consumed, so KERNEL32 uses two DWORDs (64 total BITS) to record which slot is available and which slot is already in use. These two dwords can be imagined as a 64-bit array, and if a bit is established, it means that its corresponding TLS slot is already in use. This 64-bit TLS slot array is stored in the process database (the PDB structure).
It should be known that the operating system uses a structure to describe the thread, which is often called TEB (thread environment Block), each thread has a corresponding TEB, and when the thread is switched, it switches to a different teb. There is a pointer value pointing to the current TEB, changing the value of the pointer when switching threads, so that access to thread-related values can be unified from this pointer value. What are some of the variables in TEB? One of the variables is a pointer to the thread TLS array. Called _tls_array, you can use this array to manage thread-related data. We can already get our own _tls_array in different threads, when we want to access the elements of the array, but also the index. At this time, look at TlsAlloc, you should be very clear what it means? Yes, it says, assign me an index number indicating that the corresponding array item is already in use. TlsFree, which is to release the index number, indicates that the corresponding array item can be reused. Tlssetvalue,tlsgetvalue is to take an index and set a value or value to the corresponding array item.
Thread-local storage has different implementations on different platforms, and portability is not very good. Fortunately, to implement thread-local storage is not difficult, the simplest way is to create a global table, through the current thread ID to query the corresponding data, because each thread's ID is different, the data found naturally different. Most platforms provide a method of thread-local storage, and we do not need to implement them ourselves:
WIN32 implementation
Windows is identified based on the thread-local storage index (the allocation and release of this identity is done by TlsAlloc and TlsFree), with this "identity" You can call TlsGetValue or tlssetvalue in each thread to read or set the respective values of each thread;
(1) The TlsAlloc function must first be called:
DWORD TlsAlloc (void);
This function allows the system to retrieve the bit flags in the process and find a free flag, then the system will change the flag from free to inuse and let TlsAlloc return the index of the flag in the in-place array.
A DLL (or application) typically stores this index in a global variable. Because this value is used throughout the process address range rather than within the thread scope, global variables are a better choice in this case.
If TlsAlloc cannot find a free flag in the list, it returns tls_out_of_indexes (defined as 0xFFFFFFFF in WinBase.h).
When a system creates a thread, it allocates tls_minimum_available pvoid values, initializes them to 0, and associates them with the thread. Each thread has its own pvoid array, and each pvoid in the array can hold any value. Before we can save information to the thread's pvoid array, we must know which index in the array is available---this is the purpose of calling TlsAlloc. TlsAlloc reserved an index for us, if it is 2, that is, the TlsAlloc return value is 2, then no matter whether it is a thread that is currently running in the process or a thread that may be created in the future, the index 2 is no longer available.
(2) In order to put a value into the thread's pvoid array, the TlsSetValue function should be called:
BOOL WINAPI TlsSetValue (
__in DWORD Dwtlsindex,//index value, representing the specific position in the array
__in_opt lpvoid Lptlsvalue//value to set
);
When a thread calls the TlsSetValue function successfully, it modifies its own pvoid array, but it cannot modify the TLS value of another thread. When calling TlsSetValue, we should always pass in the previous index that was returned when we called TlsAlloc. Because Windows has sacrificed the error detection of input values for efficiency.
(3) In order to retrieve a value from the thread's array, the function TlsGetValue should be called:
LPVOID WINAPI TlsGetValue (
__in DWORD dwtlsindex//Index value
);
This function returns the value saved in the TLS element indexed to Dwtlsindex. TlsGetValue only looks at the array that belongs to the calling thread.
(4) When an already scheduled TLS element is no longer needed, the TlsFree function should be called:
BOOL WINAPI TlsFree (
__in DWORD dwtlsindex//Index value
);
POSIX implementations: Like Windows, the key here is equivalent to the dwtlsindex above.
int pthread_key_create (pthread_key_t * key, Void (*) (void *));
int Pthread_key_delete (pthread_key_t);
void *pthread_getspecific (pthread_key_t);
int pthread_setspecific (pthread_key_t, const void *);
Function: It is primarily intended to avoid conflicts caused by multiple threads simultaneously visiting the same global variable or static variable, especially if multiple threads need to modify this variable at the same time. To solve this problem, we can use the TLS mechanism to provide a copy of the variable value for each thread that uses the global variable, each of which can independently change its own copy without conflicting with the other thread's copy. From a thread's point of view, it's as if every thread has exactly the variable. From a global variable point of view, it is as if a global variable is cloned into multiple copies, and each copy can be independently changed by one thread.
Common scenarios:
For example, you might have a multithreaded program in which each thread writes files to different files (and therefore they use different file handle). In this case, it would be convenient to store the file handle used by each thread in TLS. When the thread needs to know the handle used, it can be obtained from TLS. The point is: the code that the thread uses to get the file handle is the same in any case, and the file handle from TLS is different. Very dexterous, isn't it? It is convenient to have global variables, but it belongs to each thread.
The following is the instance code that uses dynamic TLS in the application:
Example one:
#include <windows.h>
#include <stdio.h>
#define THREADCOUNT 4
DWORD Dwtlsindex;
VOID Errorexit (LPSTR);
void Commonfunc (void)
{
LPVOID Lpvdata;
Retrieve a data pointer for the current thread.
Lpvdata = TlsGetValue (Dwtlsindex);
if ((Lpvdata = = 0) && (GetLastError ()! = ERROR_SUCCESS))
Errorexit ("TlsGetValue error");
Use the data stored to the current thread.
printf ("Common:thread%d:lpvdata=%lx\n",
GetCurrentThreadID (), lpvdata);
Sleep (5000);
}
DWORD WINAPI ThreadFunc (VOID)
{
LPVOID Lpvdata;
Initialize the TLS index for this thread.
Lpvdata = (LPVOID) LocalAlloc (LPTR, 256);
if (! TlsSetValue (Dwtlsindex, Lpvdata))
Errorexit ("TlsSetValue error");
printf ("Thread%d:lpvdata=%lx\n", GetCurrentThreadID (), lpvdata);
Commonfunc ();
Release the dynamic memory before the thread returns.
Lpvdata = TlsGetValue (Dwtlsindex);
if (Lpvdata! = 0)
LocalFree ((hlocal) lpvdata);
return 0;
}
int main (VOID)
{
DWORD Idthread;
HANDLE Hthread[threadcount];
int i;
Allocate a TLS index.
if ((Dwtlsindex = TlsAlloc ()) = = tls_out_of_indexes)
Errorexit ("TlsAlloc failed");
Create multiple threads.
for (i = 0; i < threadcount; i++)
{
Hthread[i] = CreateThread (NULL,//default security attributes
0,//use default stack size
(Lpthread_start_routine) ThreadFunc,//thread function
NULL,//no thread function argument
0,//Use default creation Flags
&idthread); Returns thread identifier
Check The return value for success.
if (hthread[i] = = NULL)
Errorexit ("CreateThread error\n");
}
for (i = 0; i < threadcount; i++)
WaitForSingleObject (Hthread[i], INFINITE);
TlsFree (Dwtlsindex);
return 0;
}
VOID errorexit (LPSTR lpszmessage)
{
fprintf (stderr, "%s\n", lpszmessage);
ExitProcess (0);
}
Example two:
#include <stdio.h>
#include <windows.h>
#include <process.h>
Using TLS to record the running time of a thread
DWORD G_tlsusedtime;
void Initstarttime ();
DWORD Getusedtime ();
UINT __stdcall ThreadFunc (LPVOID)
{
int i;
Initialization start time
Initstarttime ();
Simulate long-time work
i = 10000*10000;
while (i--) {}
Print out the time that this thread ran
printf ("This thread was coming to end. Thread ID:%-5d, used time:%d \ n ",
:: GetCurrentThreadID (), Getusedtime ());
return 0;
}
int main (int argc, char* argv[])
{
UINT uId;
int i;
HANDLE H[10];
Initializes the thread run time recording system by requesting an index in the process bit array
G_tlsusedtime =:: TlsAlloc ();
Make 10 threads run simultaneously and wait for their respective output results
for (i=0; i<10; i++)
{
H[i] = (HANDLE):: _beginthreadex (NULL, 0, threadfunc, NULL, 0, &UID);
}
for (i=0; i<10; i++)
{
:: WaitForSingleObject (H[i], INFINITE);
:: CloseHandle (H[i]);
}
Frees the resources used by the time-logging system by releasing the thread-local storage index
:: TlsFree (G_tlsusedtime);
return 0;
}
Start time of the initialization thread
void Initstarttime ()
{
Gets the current time, and the thread's creation time is associated with the thread object
DWORD Dwstart =:: GetTickCount ();
:: TlsSetValue (G_tlsusedtime, (LPVOID) dwstart);
}
Gets the time that a thread has run
DWORD Getusedtime ()
{
Gets the current time, returning the difference between the current time and the thread creation time
DWORD dwelapsed =:: GetTickCount ();
dwelapsed = dwelapsed-(DWORD):: TlsGetValue (G_tlsusedtime);
return dwelapsed;
}
Thread-local Storage (TLS)