3 design your own thread local storage

TLS

In the actual application process, TLS (Thread Local Storage) is often used to save the pointers associated with each thread. A set of data pointed to by the pointer is applied in the process heap. This ensures that each thread only accesses the memory unit pointed to by its associated pointer. In order to simplify the process of using TLS, we want TLS to have the following two characteristics:

(1) Automatically manage the allocation and release of the memory unit pointed to by the saved pointer. On the one hand, this greatly facilitates the use of users. On the other hand, when a thread does not use thread local variables, the management system can decide not to allocate memory for this thread, so as to save memory space.
(2) Allows users to request the use of any number of TLS indexes. Microsoft ensures that each process has at least TLS in its bit group_ MINIMUM_ Available bit flags are available. In the WinNT.h file, this value is defined as 64, and Windows 2000 has extended it to make at least 1000 flags available.

Obviously, in order to realize these new features, a new "TLS" must be developed. This section describes the design process of the whole system.

In general, the new "TLS" is mainly composed of four classes, of which CSimpleList class is responsible for realizing the simple linked list function and connecting the private data of each thread so as to release the memory occupied by them; The CNoTrackObject class overloads the new and delete operators and is responsible for allocating memory space for thread private data; CThreadSlotData class is the core of the whole system. It is responsible for allocating indexes and accessing thread private data; CThreadLocal is the final class template provided to users. It is responsible for providing users with friendly interface functions.

CSimpleList class
From the perspective of use, through a global index, Windows TLS only allows users to save a 32-bit pointer, while the improved system allows users to save any type of data (including the whole class). The memory occupied by this data of any size is allocated in the process heap. Therefore, when the user releases the global index, the system must release the memory occupied by this data in each thread, which requires the system to record the memory allocated for each thread.

A better method is to connect the first addresses of each private data with a linked list. When releasing the global index, you can free the space occupied by thread private data one by one by traversing the linked list. For example, there is a data structure for storing thread private data.

struct CThreadData 
{ 
     CThreadData* pNext; // Pointer to the CThreadData structure of the next thread
     LPVOID pData; // Pointer to real thread private data
};

The pointer pData points to the first address of the memory allocated for the thread, and the pointer pNext connects the data of each thread, as shown in Figure 3.9. The entire table can be managed by the first CThreadData structure's first address, pHead.

Connect the data of each CThreadData structure into a table. Removing, adding or obtaining sections in the table are only related to pNext members. In other words, all operations on this table depend on accessing the value of the pNext member. As long as the offset of pNext member in CThreadData structure is specified, the whole linked list can be operated. More generally, given a data structure, as long as you know the offset of pNext member in the data structure (this structure must contain a pNext member), you can connect the data conforming to this structure type into a table. This function has strong independence, so it is necessary to write a class to manage the pNext members in the data structure, and then manage the whole linked list.

The only information this class needs to know is the offset of the pNext member in the CThreadData structure. After specifying the offset, it can get the address of the pNext variable in each data structure and access its value. If you record the address of the first data in the linked list, the whole class can be basically realized. Therefore, the members used to implement the class are designed as the following three.

void* m_pHead; // The address of the first element in the linked list
size_t m_nNextOffset; // The offset of the pNext member in the data structure
void** GetNextPtr(void* p) const 
{ 
    return (void**)((BYTE*)p + m_nNextOffset); 
}

GetNextPtr function through offset m_nNextOffset gets the address of the pNext pointer, so its return value is the pointer to the pointer. For example, if a pointer to CThreadData type data is passed, the GetNextPtr function adds the offset to the address to get the address of the member pNext pointer.

Since this class only implements the function of a simple linked list, it is named CSimpleList. Starting with the letter "C" is a rule for class naming in this book, such as CWinThread, CWnd class, etc. Now we are concerned about what interface functions the CSimpleList class should provide to users.

The general operations on the linked list include adding, deleting and traversing the elements in the list. Although it is only a simple management of tables, the CSimpleList class should also implement these functions. The member functions that complete these functions are the interfaces provided to users.

Naming the files that define classes and implement classes is also an important thing. Similarly, we should follow some rules to keep many files together without confusion. These classes are encapsulated to implement the final TLS system, so we name the class definition file designed in this section as_ AFXTLS_.H. The English word corresponding to "AFX" is Application Framework, and the remaining "X" is used for recharge (a group of three letters is better than two letters). In the future, the file names of header files in our class library will start with "AFX". Do not remove the "" symbol of the first one, or it will conflict with the file name in MFC (abbreviation for Microsoft Foundation Classes). MFC is a class library of VC. Now is not the time to introduce it. Here is_ AFXTLS_. All contents in the H header file.

#ifndef __AFXTLS_H__  // _AFXTLS_.H file
#define __AFXTLS_H__

#include <windows.h>
#include <stddef.h>

class CSimpleList
{
public:
	CSimpleList(int nNextOffset = 0);
	void Construct(int nNextOffset);

//The interface functions (Operations) provided to the user are used to add, delete and traverse nodes
	BOOL IsEmpty() const;
	void AddHead(void* p);
	void RemoveAll();
	void* GetHead() const;
	void* GetNext(void* p) const;
	BOOL Remove(void* p);

//Implementation required to implement interface functions
	void* m_pHead;		//The address of the first element in the linked list
	size_t m_nNextOffset;	//The offset of the pNext member in the data structure
	void** GetNextPtr(void* p) const;
};

// Inline function of class
inline CSimpleList::CSimpleList(int nNextOffset)
{ m_pHead = NULL; m_nNextOffset = nNextOffset; }

inline void CSimpleList::Construct(int nNextOffset)
{ m_nNextOffset = nNextOffset; }

inline BOOL CSimpleList::IsEmpty() const
{ return m_pHead == NULL; }

inline void CSimpleList::RemoveAll()
{ m_pHead = NULL; }

inline void* CSimpleList::GetHead() const
{ return m_pHead; }

inline void* CSimpleList::GetNext(void* preElement) const
{ return *GetNextPtr(preElement); }

inline void** CSimpleList::GetNextPtr(void* p) const
{ return (void**)((BYTE*)p + m_nNextOffset); }

#endif // __AFXTLS_H__

To avoid duplicate inclusion, the AFXTLS.H header file uses the following set of precompiled instructions.

#ifndef __AFXTLS_H__ // _AFXTLS_.H file
#define __AFXTLS_H__ 
............ // Specific statement
#endif // __AFXTLS_H__

Compilation preprocessing is an important tool for modular programming. The precompiled instruction above means that if no macro name is defined__ AFXTLS_H__, Define the macro name and compile #endif the previous code; If macro name is defined__ AFXTLS_H__ If so, the compiler will do nothing to #endif the previous code. This prevents header files_ AFXTLS_. The code in H is repeatedly included (which will cause compilation errors). The macro name that identifies whether each header file is included should follow certain rules and adopt meaningful strings to improve the readability of the program and avoid the reuse of macro names. If there are no special circumstances, it is best to use the naming rules used in this book.

Implement the most critical member m of the CSimpleList class_ Nnextoffset is initialized to the user specified value in the constructor of the class. Member variable m_ Pahead should be initialized to NULL, indicating that the whole linked list is empty.

AddHead function is used to add an element to the linked list and put the newly added element in the header. Its implementation code is in the AFXTLS.CPP file.

void CSimpleList::AddHead(void* p)
{
	*GetNextPtr(p) = m_pHead;
	m_pHead = p;
}

For example, the user passes a pointer p of CThreadData type for this function. Before setting p as the new header, the AddHead function must make the pNext member of p point to the original header. Here, the GetNextPtr function obtains the address of the pNext member in the CThreadData structure pointed to by p. By continuously calling the AddHead function, all CThreadData types can be linked into a linked list.

The RemoveAll function is the simplest. It only takes the member M_ The value of phead is set to NULL, which means that the whole linked list is empty. The GetHead function is used to obtain the pointer of the header element in the table. It is used together with the GetNext function to traverse the entire linked list.

The last one is the Remove function, which is used to delete a specified element from the table, and the implementation code is in the AFXTLS.CPP file.

BOOL CSimpleList::Remove(void* p)
{
	if(p == NULL)	//Check parameters
		return FALSE;
	
	BOOL bResult = FALSE; //Suppose the removal fails
	if(p == m_pHead)
	{
	//To remove a header element
		m_pHead = *GetNextPtr(p);
		bResult = TRUE;
	}
	else
	{
		//An attempt was made to find an element to remove in a table
		void* pTest = m_pHead;
		while(pTest != NULL && *GetNextPtr(pTest) != p)
			pTest = *GetNextPtr(pTest);

		//If found, remove the element
		if(pTest != NULL)
		{
			*GetNextPtr(pTest) = *GetNextPtr(p);
			bResult = TRUE;
		}
	}
	return bResult;
}

Also assume that parameter p is a pointer of type CThreadData. According to the position of the element to be removed in the table, it should be handled in three cases:
(1) This element is the header element. At this time, only the next element can be used as the header element.
(2) This element is in the linked list but not the header element. In this case, after the search, the element referred to by the pTest pointer is the previous element to be deleted, so just let the pNext member of the element referred to by pTest point to the next element to be deleted.
(3) The element to be deleted does not exist in the table at all.

The following small example illustrates the method of building a linked list using the CSimpleList class. It connects a set of user-defined data into a linked list through the CSimpleList class, then traverses the linked list and prints out the values of the linked list items. The source code is as follows.

template<class TYPE>
class CTypedSimpleList : public CSimpleList
{
public:
	CTypedSimpleList(int nNextOffset = 0) 
		: CSimpleList(nNextOffset) { }
	void AddHead(TYPE p) 
		{ CSimpleList::AddHead((void*)p); }
	TYPE GetHead()
		{ return (TYPE)CSimpleList::GetHead(); }
	TYPE GetNext(TYPE p)
		{ return (TYPE)CSimpleList::GetNext(p); }
	BOOL Remove(TYPE p)
		{ return CSimpleList::Remove(p); }
	operator TYPE()
		{ return (TYPE)CSimpleList::GetHead(); }
};

CTypedSimpleList class overloads the functions with data type interfaces in CSimpleList class, and performs type conversion internally. The above example will be simpler if it is implemented with the CTypedSimpleList class. Here are some code snippets after rewriting.

MyThreadData* pData; 
CTypedSimpleList<MyThreadData*> list; // Note the format of the class template object
list.Construct(offsetof(MyThreadData, pNext)); 
// Add member to linked list
for(int i=0; i<10; i++) 
{ //... ibid.} 
 // ...... / / use the data in the linked list
 // Traverse the entire linked list to free up the space occupied by the MyThreadData object
pData = list; // The member function operator TYPE() is called, which is equivalent to the "pData = list.GetHead();" statement
while(pData != NULL) 
{ //... ibid.}

The execution result of this code is the same as that of the above code. In the statement "pData = list", the program directly refers to the object of CTypedSimpleList class, which will cause its member function operator TYPE() to be called and return the pointer (header pointer) of the first element in the linked list.

CNoTrackObject class
The previous section solves the problem of connecting the memory occupied by private data of multiple threads. This section studies how to allocate memory for thread private data.

The default version of operator new in C + + language is a general-purpose memory allocator. It must be able to allocate memory blocks of any size. Similarly, operator delete should be able to release memory blocks of any size. If operator delete wants to find out how much memory it wants to release, it must know how much memory was allocated by operator new. There is a common method that allows operator new to tell operator delete the size of the originally allocated memory, that is, some additional information is pre attached to the returned memory to indicate the size of the allocated memory block. That is, when the following statement is written:

CThreadData* pData = new CThreadData;

The result is a memory block like this:

When running in the debugging environment, operator new adds more additional memory. It needs to record the file and line number of the code using new to apply for memory, so as to track the memory leakage.

The memory used by thread private data is automatically allocated internally for users by our system. When using private data lines
At the end of the process, the system will also automatically release this memory for the user. Users don't have to care about this process. They just need to use thread private data when they want. It is the responsibility of our TLS system to ensure that the user's operation will not cause memory leakage. Since we can ensure that memory leaks do not occur, there is no need to track memory usage. In order to save memory space, there is no need to record the size of memory. All this shows that you should write the implementation code of operator new and operator delete, and directly use API functions to provide a low_level allocator.

To ensure that the allocation and release of all thread private data memory are completed by the rewritten new and delete operators,
This is very simple. Write a base class that overloads the new and delete operators so that the structures used by all threads' private data can be inherited from this class. The class name of this class should reflect that the memory used by it is not tracked, so it is named CNoTrackObject. The following is the definition and implementation code of CNoTrackObject class, or_ AFXTLS.H and AFXTLS.CPP files.

//_ AFXTLS.H file
class CNoTrackObject
{
public:
	void* operator new(size_t nSize);
	void operator delete(void*);
	virtual ~CNoTrackObject() { }
};

//AFXTLS.CPP file
void* CNoTrackObject::operator new(size_t nSize)
{
	// Apply for a piece with GMEM_FIXED and GMEM_ Memory with zeroinit flag
	void* p = ::GlobalAlloc(GPTR, nSize);
	return p;
}

void CNoTrackObject::operator delete(void* p)
{
	if(p != NULL)
		::GlobalFree(p);
}

The GlobalAlloc function is used to allocate memory space of a specified size in the default heap of the process. The prototype is as follows.

HGLOBAL GlobalAlloc( 
  UINT uFlags, //Specify memory properties
  SIZE_T dwBytes //Execution memory size
);

The uFlags parameter can be a combination of the following values:
● GHND GMEM_MOVEABLE and GMEM_ Combination of zeroinit
● GMEM_FIXED applies for fixed memory, and the return value of the function is the memory pointer
● GMEM_MOVEABLE applies for removable memory. The return value of the function is the handle to the memory object. In order to convert the handle into a pointer, use the GlobalLock function
● GMEM_ZEROINIT initialization memory content is 0
● GPTR GMEM_FIXED and GMEM_ Combination of zeroinit
The removable memory block will never be in physical memory. Before using it, you must call the GlobalLock function to lock it to physical memory. The GlobalUnlock function should be called to unlock when it is no longer in use.

The GlobalFree function frees the memory space allocated by the GlobalAlloc function.

Taking CThreadData structure as an example, in order to use the memory allocator provided by CNoTrackObject class to allocate the memory occupied by its object, it needs to be defined as a derived class of CNoTrackObject class.

Insert the code slice here

With the above definition, when the following code appears in the program, Windows will call the functions in the CNoTrackObject class to manage memory.

CThreadData* pData = new CThreadData; 
// ... / / use CThreadData object
delete pData;

When the C + + compiler sees the first line, it checks whether the CThreadData class contains or inherits member functions that overload the new operator. If it does, the compiler will produce code that calls the function. If the compiler cannot find a function that overloads the new operator, it will produce code that calls the standard new operator function. The compiler treats the delete operator the same way. Of course, the above code will call the operator new function in the CNoTrackObject class to apply for memory space for the CThreadData object.

This is perfect. Any class derived from CNoTrackObject can be used as the data type of thread private data. Our system also requires the user to define the type of thread private data.

CThreadSlotData class
As mentioned above, the memory used by thread local storage starts with a CThreadData structure, and its member pointer pData points to the real thread private data. If the space pointed to by pData is divided into multiple slots, and a thread private data pointer is placed in each slot, each thread can be allowed to store any thread private pointer.

It's easy to divide the space pointed to by pData into multiple slots. Just regard the space as an array of PVOID type. Each element in the array holds a pointer, that is, the thread private data pointer, which points to the memory block allocated in the heap that really stores thread private data. The information to be saved in the CThreadData structure is the first address of the pointer array and the number of arrays, so rewrite the CThreadData structure as follows.

struct CThreadData : public CNoTrackObject
{
	CThreadData* pNext; //This member is used by the CSimpleList class
	int nCount;	    //Number of array elements
	LPVOID* pData;      //First address of array
};

Now, the thread private space of each thread will start with this new CThreadData structure. pData is the first address of a PVOID type array. The elements of the array store pointers to thread private data. The nCount member records the number of this array, that is, the number of slots. Their relationship is shown in Figure 3.11.

The figure above illustrates the final form of the TLS system we designed to save thread private data. When the user requests to access the private data of this thread, it should explain which slot number corresponds to the thread private data to be accessed. Internally, our system first obtains the first address of the CThreadData structure in the thread, and then takes the slot number provided by the user as the subscript of pData to obtain the address of the thread private data corresponding to the slot number. There are two problems to be solved:
(1) How to assign slot numbers to users.
(2) How to save the first address of CThreadData structure in each thread.

The second problem is easy to solve. Just use the TLS mechanism provided by Windows to apply for a global index to save the first address of CThreadData structure for each thread.

To solve the first problem, you can follow the practice of Windows when implementing TLS, apply for a bit group in the process, the subscript of the array represents the slot number, and the value of its member indicates whether the slot number is allocated. Each process has only one copy of this array. Of course, it can be used not only to indicate whether the slot is occupied, but also to indicate other information, such as the module occupying the slot. Just change the data type of the array. Our system uses a global array to indicate which slot is allocated and which module is allocated, so the data type of the array should be like this.

struct CSlotData 
{ 
    DWORD dwFlags; // Slot usage flag (assigned / unassigned)
    HINSTANCE hInst; // The module handle that occupies this slot
};

Applying for an array of CSlotData type in the process can manage the slot number used by each thread. Never mind the value of the hInst member (always set it to NULL now). We specify that when the value of dwFlags is 0x01, it means that the slot is allocated, and when it is 0, it means that the slot is not allocated. In this way, the problem of assigning slot numbers to users has also been solved.

So far, the functions of our TLS system can be basically realized. According to the previous design, a class should be written to manage the pointers to the thread private data corresponding to each slot. Specifically, this class should be responsible for the allocation of the global slot number and the access of the data in the slot in each thread, so it is named CThreadSlotData.

CThreadSlotData class is based on Win32 TLS to save the pointer of thread private data for each thread, so there should be a member variable used as TLS index in the class.

DWORD m_tlsIndex;

In the constructor of the class, we call the TlsAlloc function to allocate the index, and in the destructor, we call the TlsFree function to release the index. When the user accesses the thread private variable for the first time in the thread, our system needs to apply for memory space for the thread private variable and use m_tlsIndex calls the TlsSetValue function for the parameter to save the address of this memory space.

In order to concatenate the private data of each thread, we also use the CTypedSimpleList class.

CTypedSimpleList<CThreadData*> m_list;

m_ The list member variable is used to set the value of the pNext member in the CThreadData structure of the private data header of each thread. After applying for memory space for the private data of a thread, m should be called immediately_ The list.addhead function adds the first address of the thread's private space to M_ In the linked list maintained by the list member.

The members responsible for managing the global flag array are as follows.

int m_nAlloc; //m_ The size of the array pointed to by pslotdata
int m_nMax; //Maximum number of slots occupied
CSlotData* m_pSlotData; //First address of global array

m_pSlotData is the first address of the array. Because the array is dynamically allocated, its size should also be recorded. Member m_nAlloc indicates the number of array members, m_nMax represents the maximum number of slots occupied so far. For example, in one time, the size of CSlotData array is 6, and its slot usage is as follows:

At this time, M_ The value of nalloc is 6 and m_ The value of nmax is 4. Because M_ The value of nmax is the number of slots from Slot0 to the last used slot (assuming that Slot4 is not used). Each thread that uses thread private data has a CThreadData structure. The space pointed to by the member pData in the structure is divided into several slots. How many slots are divided is determined by the state of the array globally responsible for allocating slots. In order to save memory space, we specify that M_ The value of nmax is the number of slots allocated by each thread for its private data.

The interface functions provided by the CThreadSlotData class to the user should also correspond to the functions provided by the TLS of Windows, providing the functions of allocating and releasing slot numbers and accessing thread private data through slot numbers. After designing the implementation process and interface function of CThreadSlotData class, it is easy to write code to complete the whole class. The following is the code defining the class, which should also be saved in_ AFXTLS.H file.

//CThreadSlotData - manage our own thread local storage / /_ AFXTLS.H file
struct CSlotData;
struct CThreadData;

class CThreadSlotData
{
public:
	CThreadSlotData();

// Interface functions (Operations) provided to users
	int AllocSlot();	
	void FreeSlot(int nSlot); 
	void* GetThreadValue(int nSlot); 
	void SetValue(int nSlot, void* pValue);
	void DeleteValues(HINSTANCE hInst, BOOL bAll = FALSE);

// Class implementation code
	DWORD m_tlsIndex;	// Used to access thread local storage provided by the system

	int m_nAlloc;		//  m_ The size of the array pointed to by pslotdata
	int m_nRover;		// The value set to quickly find an empty slot
	int m_nMax;		// The size of the array pointed to by pData in the CThreadData structure
	CSlotData* m_pSlotData;	// The first address of the global array that identifies the status of each slot
	CTypedSimpleList<CThreadData*> m_list;	// List of CThreadData structures
	CRITICAL_SECTION m_cs;

	void* operator new(size_t, void* p)
			{ return p; }
	void DeleteValues(CThreadData* pData, HINSTANCE hInst);
	~CThreadSlotData();
};

m_cs is a key segment variable that is initialized in the constructor of the class. Because the object defined by CThreadSlotData class is a global variable, it must pass m_cs to synchronize concurrent access to the variable by multiple threads.

The implementation code of the functions in the class is in the AFXTLS.CPP file, which is discussed one by one below.

//-------------------CThreadSlotData class----------------------//
BYTE __afxThreadData[sizeof(CThreadSlotData)];	// For the following_ The afxThreadData variable provides memory
CThreadSlotData* _afxThreadData; // Define global variables_ afxThreadData to allocate space for global variables

struct CSlotData
{
	DWORD dwFlags;	// Slot usage flag (assigned / unassigned)
	HINSTANCE hInst;// The module handle that occupies this slot
};

struct CThreadData : public CNoTrackObject
{
	CThreadData* pNext; // This member is used by the CSimpleList class
	int nCount;	    // Number of array elements
	LPVOID* pData;      // First address of array
};

#define SLOT_USED 0x01 		//  When the value of dwFlags member in CSlotData structure is 0x01, it indicates that the slot has been used

CThreadSlotData::CThreadSlotData()
{
	m_list.Construct(offsetof(CThreadData, pNext)); // Initializes the CTypedSimpleList object

	m_nMax = 0;
	m_nAlloc = 0;
	m_nRover = 1;	// We assume that Slot1 has not been allocated (the first slot (Slot0) is always reserved and not used)
	m_pSlotData = NULL;

	m_tlsIndex = ::TlsAlloc();	// Use the system's TLS to request an index
	::InitializeCriticalSection(&m_cs);	// Initialize key segment variables
}

First, define a global variable of type CThreadSlotData_ afxThreadData to allocate thread local storage space for the threads of the process. CThreadSlotData class also overloads the new operator, but the operator new function does not really allocate space for the CThreadSlotData object, but only returns the pointer in the parameter as the first address of the object. For example, initialization_ The code used for the afxThreadData pointer is as follows.

_afxThreadData = new(__afxThreadData) CThreadSlotData;

The syntax of the new expression specified in the C + + language standard is:

[::] new [placement] new-type-name [new-initializer]

If the overloaded new function has a division of size_ For parameters other than t, write them in the placement field. The type name field specifies the type of object to be initialized. After calling operator new, the compiler also calls the constructor in this type to initialize the object. If the constructor of a class needs to pass parameters, it should be specified in the initializer field.

When using our thread local storage system, users must first call AllocSlot to apply for a slot number. The following is the code to implement the AllocSlot function.

int CThreadSlotData::AllocSlot()
{
	::EnterCriticalSection(&m_cs);	// Enter critical zone (also known as critical section)
	int nAlloc = m_nAlloc;
	int nSlot = m_nRover;

	if(nSlot >= nAlloc || m_pSlotData[nSlot].dwFlags & SLOT_USED)
	{
		// Search m_pSlotData to find an empty SLOT (SLOT)
		for(nSlot = 1; nSlot < nAlloc && m_pSlotData[nSlot].dwFlags & SLOT_USED; nSlot ++) ;

		// If there is no empty slot, apply for more space
		if(nSlot >= nAlloc)
		{
			// Increase the size of the global array and allocate or reallocate memory to create new slots
			int nNewAlloc = nAlloc + 32;

			HGLOBAL hSlotData;
			if(m_pSlotData == NULL)	// First use
			{
				hSlotData = ::GlobalAlloc(GMEM_MOVEABLE, nNewAlloc*sizeof(CSlotData));
			}
			else
			{
				hSlotData = ::GlobalHandle(m_pSlotData);
				::GlobalUnlock(hSlotData);
				hSlotData = ::GlobalReAlloc(hSlotData, 
					nNewAlloc*sizeof(CSlotData), GMEM_MOVEABLE);
			}
			CSlotData* pSlotData = (CSlotData*)::GlobalLock(hSlotData);
	
			// Initialize the newly requested space to 0
			memset(pSlotData + m_nAlloc, 0, (nNewAlloc - nAlloc)*sizeof(CSlotData));
			m_nAlloc = nNewAlloc;
			m_pSlotData = pSlotData;
		}
	}

	// Adjustment M_ The value of nmax to allocate memory for the private data of each thread
	if(nSlot >= m_nMax)
		m_nMax = nSlot + 1;

	m_pSlotData[nSlot].dwFlags |= SLOT_USED;
	// Update M_ Value of nlrover (we assume that the next slot is not used)
	m_nRover = nSlot + 1;

	::LeaveCriticalSection(&m_cs);
	return nSlot; // The returned slot number can be used by freeslot, getthreadvalue and setValue functions
}

Member variable m_ Nlrover is set up to quickly find a slot that is not used. We always assume that the next slot of the currently allocated slot is not used (most of the time).

When entering for the first time, we apply for a sizeof(CSlotData)*32 space to represent the status of each slot. A total of 31 slot numbers can be allocated to users in this space (Slot0 is reserved). If the user needs to allocate slot numbers after using these 31 slots, we will re apply for memory space to meet the user's requirements. This dynamic memory request method allows users to use any number of slot numbers.

After getting the slot number, the user can access the private data of each thread corresponding to the slot number. This function is completed by the SetValue function. Similarly, when the user sets the value of thread private data for the first time, we apply for memory space for the thread private data. The following is the specific implementation code.

void CThreadSlotData::SetValue(int nSlot, void* pValue)
{
	// The private storage space we arranged for threads is obtained through TLS index
	CThreadData* pData = (CThreadData*)::TlsGetValue(m_tlsIndex);

	// Request memory space for thread private data
	if((pData == NULL || nSlot >= pData->nCount) && pValue != NULL)
	{
		// The value of pData is null, indicating that the thread accesses the thread private data for the first time
		if(pData == NULL)
		{
			pData = new CThreadData;
			pData->nCount = 0;
			pData->pData = NULL;

			// Add the address of the newly requested memory to the global list
			::EnterCriticalSection(&m_cs);
			m_list.AddHead(pData);
			::LeaveCriticalSection(&m_cs);
		}

		// Pdata - > pdata points to the real thread private data. The following code increases the space occupied by private data to M_ Size specified by nmax
		if(pData->pData == NULL)
			pData->pData = (void**)::GlobalAlloc(LMEM_FIXED, m_nMax*sizeof(LPVOID));
		else
			pData->pData = (void**)::GlobalReAlloc(pData->pData, m_nMax*sizeof(LPVOID), LMEM_MOVEABLE);
		
		// The newly requested memory is initially set to 0
		memset(pData->pData + pData->nCount, 0, 
			(m_nMax - pData->nCount) * sizeof(LPVOID));
		pData->nCount = m_nMax;
		::TlsSetValue(m_tlsIndex, pData);
	}

	// Sets the value of thread private data
	pData->pData[nSlot] = pValue;
}

CThreadSlotData is not the class that is ultimately provided to the user to store thread local variables. The data storage space used by real users is the memory block referred to by the pdata - > pdata [nslot] Pointer in each thread (as shown in Figure 3.11), that is, the memory pointed to by the pointer returned by the GetThreadValue function. CThreadSlotData is not responsible for creating this space, but it is responsible for freeing the memory used by this space. Therefore, when releasing an index, we also need to release the space used by real user data. The FreeSlot function below illustrates this.

void CThreadSlotData::FreeSlot(int nSlot)
{
	::EnterCriticalSection(&m_cs);	

	// Delete data from all threads
	CThreadData* pData = m_list;
	while(pData != NULL)
	{
		if(nSlot < pData->nCount)
		{
			delete (CNoTrackObject*)pData->pData[nSlot];
			pData->pData[nSlot] = NULL;
		}
		pData = pData->pNext;
	}

	// Identify this slot number as unused
	m_pSlotData[nSlot].dwFlags &= ~SLOT_USED;
	::LeaveCriticalSection(&m_cs);
}

Releasing a slot means deleting the user data corresponding to this slot in all threads. By traversing M_ The linked list managed by list can easily get the first address of CThreadData structure in each thread, and then release the memory pointed by the data in the specified slot.

When the thread ends, it is necessary to release all the space occupied by the thread's local variables. This is the key to the automatic memory management of our TLS system. CThreadSlotData class uses the DeleteValues function to release the memory occupied by one or all threads due to the use of local storage. The code is as follows.

void CThreadSlotData::DeleteValues(HINSTANCE hInst, BOOL bAll)
{
	::EnterCriticalSection(&m_cs);
	if(!bAll)
	{
		// Only delete the space occupied by the thread local storage of the current thread
		CThreadData* pData = (CThreadData*)::TlsGetValue(m_tlsIndex);
		if(pData != NULL)
			DeleteValues(pData, hInst);
	}
	else
	{
		// Delete the space occupied by thread local storage of all threads
		CThreadData* pData = m_list.GetHead();
		while(pData != NULL)
		{
			CThreadData* pNextData = pData->pNext;
			DeleteValues(pData, hInst);
			pData = pNextData;
		}
	}
	::LeaveCriticalSection(&m_cs);
}

void CThreadSlotData::DeleteValues(CThreadData* pData, HINSTANCE hInst)
{
	// Release each element in the table
	BOOL bDelete = TRUE;
	for(int i=1; i<pData->nCount; i++)
	{
		if(hInst == NULL || m_pSlotData[i].hInst == hInst)
		{
			// hInst match, delete data
			delete (CNoTrackObject*)pData->pData[i];
			pData->pData[i] = NULL;
		}
		else
		{
			// There are other modules in use. Do not delete data
			if(pData->pData[i] != NULL)
			bDelete = FALSE;
		}
	}

	if(bDelete)
	{
		// Remove from list
		::EnterCriticalSection(&m_cs);
		m_list.Remove(pData);
		::LeaveCriticalSection(&m_cs);
		::LocalFree(pData->pData);
		delete pData;

		// Clear TLS index to prevent reuse
		::TlsSetValue(m_tlsIndex, NULL);
	}
}

The comments of the code are very detailed, so I won't say more. The parsing function of class needs to free all used memory and release TLS index m_tlsIndex and remove critical zone object m_cs, the specific code is as follows.

CThreadSlotData::~CThreadSlotData()
{
	CThreadData *pData = m_list;
	while(pData != NULL)
	{
		CThreadData* pDataNext = pData->pNext;
		DeleteValues(pData, NULL);
		pData = pData->pNext;
	}

	if(m_tlsIndex != (DWORD)-1)
		::TlsFree(m_tlsIndex);

	if(m_pSlotData != NULL)
	{
		HGLOBAL hSlotData = ::GlobalHandle(m_pSlotData);
		::GlobalUnlock(hSlotData);
		::GlobalFree(m_pSlotData);
	}

	::DeleteCriticalSection(&m_cs);
}

At this point, the encapsulation of CThreadSlotData class is completed. With the support of this class, our thread local storage system can be implemented soon.

CThreadLocal class template
CThreadSlotData class does not realize the function of allocating memory space for the data used by users, which can not fulfill the original intention of allowing users to define any type of data as thread local storage variables. This section encapsulates a class template named CThreadLocal to end the design of the whole system.

CThreadLocal is the final class template provided to users. The class name literally means "thread local storage". The user manages the real user data referred to by the data in each slot in the thread through the CThreadLocal class.

Now let's focus on the design process of CThreadLocal class template. Allowing users to define any type of thread private variables is the function to be implemented by this kind of template, which includes two aspects:
(1) In the process heap, request memory space for each thread that uses thread private variables. It's simple, just use
The new operator is OK.
(2) Associate the first address of the memory space applied above with each thread object, that is, implement a thread local variable to save the memory address applied above. Obviously, the CThreadSlotData class is designed to accomplish this function.

Saving memory addresses is an independent task. It's best to encapsulate another class. The purpose of this class is to help the CThreadLocal class implement a thread private variable. We will name it CThreadLocalObject. This class is also defined in_ AFXTLS_.H file, the code is as follows.

class CThreadLocalObject
{
public:
// Attribute member (Attributes), which is used to obtain the pointer saved in the variable local to the thread
	CNoTrackObject* GetData(CNoTrackObject* (*pfnCreateObject)());
	CNoTrackObject* GetDataNA();

// Implementation
	DWORD m_nSlot;
	~CThreadLocalObject();
};

The CThreadLocalObject class is designed to provide a thread local variable. In the two interface functions, GetDataNA is used to return the value of the variable; GetData can also return the value of a variable, but if it is found that the variable has not been assigned a slot number (m_nSlot == 0), it will be assigned a slot number; If slot M_ If there is no data in the nslot (empty), call the function passed by the parameter pfnCreateObject to create a data item and save it to slot M_ In nslot. The specific implementation code is in the AFXTLS.CPP file.

CNoTrackObject* CThreadLocalObject::GetData(CNoTrackObject* (*pfnCreateObject)())
{
	if(m_nSlot == 0)
	{
		if(_afxThreadData == NULL)
			_afxThreadData = new(__afxThreadData) CThreadSlotData;
		m_nSlot = _afxThreadData->AllocSlot();
	}
 
	CNoTrackObject* pValue = (CNoTrackObject*)_afxThreadData->GetThreadValue(m_nSlot);
	if(pValue == NULL)
	{
		// Create a data item
		pValue = (*pfnCreateObject)();

		// Use thread private data to save newly created objects
		_afxThreadData->SetValue(m_nSlot, pValue);	
	}
	
	return pValue;
}

CNoTrackObject* CThreadLocalObject::GetDataNA()
{
	if(m_nSlot == 0 || _afxThreadData == 0)
		return NULL;
	return (CNoTrackObject*)_afxThreadData->GetThreadValue(m_nSlot);
}

CThreadLocalObject::~CThreadLocalObject()
{
	if(m_nSlot != 0 && _afxThreadData != NULL)
		_afxThreadData->FreeSlot(m_nSlot);
	m_nSlot = 0;
}

CThreadLocalObject class does not have an explicit constructor. Who is responsible for M_ Is the value of nslot initialized to 0? In fact, this is related to the use of this kind of. Since multiple threads use the same object of this class, of course, the user is required to define the object of CThreadLocalObject class as a global variable. All members of the global variable are automatically initialized to 0.

The last class, CThreadLocal, only needs to provide a function to apply for memory space for thread private variables, and can carry out type conversion. The following is the implementation code of CThreadLocal class.

template<class TYPE> 
class CThreadLocal : public CThreadLocalObject 
{
//Attributes
public: 
      TYPE* GetData() 
      { 
           TYPE* pData = (TYPE*)CThreadLocalObject::GetData(&CreateObject); 
           return pData; 
      } 
      TYPE* GetDataNA() 
      { 
           TYPE* pData = (TYPE*)CThreadLocalObject::GetDataNA(); 
           return pData; 
      } 
      operator TYPE*() 
      { 
           return GetData();
      } 
      TYPE* operator->() 
      { 
           return GetData(); 
      } 
// Implementation
public: 
      static LPVOID CreateObject() 
      { 
           return new TYPE; 
      } 
};

The CThreadLocal template can be used to declare thread private variables of any type, because the pointer type can be automatically and correctly cast through the template. The member function CreateObject is used to create an object of a dynamically specified type. The member function GetData calls the function with the same name of the base class CThreadLocalObject, and passes the address of the CreateObject function to it as a parameter.

In addition, the CThreadLocal template overloads the operation symbols "*" and "- >", so that the compiler will automatically type
transformation.

When using CThreadLocal class template, first derive a class (structure) from CNoTrackObject class, and then use this class as the parameter of CThreadLocal class template to define thread local variables. Here is a specific example. This example creates 10 worker threads. These threads first set the value of thread private data, and then print the previously set value through a public user-defined function ShowData. It can be seen that the values of thread private data in different threads can be different. The program code is as follows.

// MyTls.cpp file
#include "_afxtls_.h"
#include <process.h>

#include <iostream>
using namespace std;

struct CMyThreadData : public CNoTrackObject
{
	int nSomeData;
};

// After the following code is expanded, it is equivalent to "cthreadlocal < cmythreaddata > g_mythreaddata;"
THREAD_LOCAL(CMyThreadData, g_myThreadData)

void ShowData();
UINT __stdcall ThreadFunc(LPVOID lpParam)
{
	g_myThreadData->nSomeData = (int)lpParam;
	ShowData();
	return 0;
}

void main()
{
	HANDLE h[10];
	UINT uID;

	// Start ten threads and pass i as the parameter of the thread function
	for(int i = 0; i < 10; i++)
		h[i] = (HANDLE) ::_beginthreadex(NULL, 0, ThreadFunc, (void*)i, 0, &uID);
	::WaitForMultipleObjects(10, h, TRUE, INFINITE);
	for(int i = 0; i < 10; i++)
		::CloseHandle(h[i]);
}

void ShowData()
{
	int nData = g_myThreadData->nSomeData;
	printf(" Thread ID: %-5d, nSomeData = %d \n", ::GetCurrentThreadId(), nData);
}

Using CThreadLocal to realize thread local storage is much more convenient. However, our own TLS system does not release the memory occupied by the thread's data after the thread that has used the thread's private data runs. This causes a memory leak.
_AFXTLS_.H

// _ AFXTLS_.H file
#ifndef __AFXTLS_H__  // _AFXTLS_.H file
#define __AFXTLS_H__

#include <windows.h>
#include <stddef.h>


class CNoTrackObject;


// CSimpleList

class CSimpleList
{
public:
	CSimpleList(int nNextOffset = 0);
	void Construct(int nNextOffset);

// The interface functions (Operations) provided to the user are used to add, delete and traverse nodes
	BOOL IsEmpty() const;
	void AddHead(void* p);
	void RemoveAll();
	void* GetHead() const;
	void* GetNext(void* p) const;
	BOOL Remove(void* p);

// Implementation required to implement interface functions
	void* m_pHead;		// The address of the first element in the linked list
	size_t m_nNextOffset;	// The offset of the pNext member in the data structure
	void** GetNextPtr(void* p) const;
};

// Inline function of class
inline CSimpleList::CSimpleList(int nNextOffset)
{ m_pHead = NULL; m_nNextOffset = nNextOffset; }

inline void CSimpleList::Construct(int nNextOffset)
{ m_nNextOffset = nNextOffset; }

inline BOOL CSimpleList::IsEmpty() const
{ return m_pHead == NULL; }

inline void CSimpleList::RemoveAll()
{ m_pHead = NULL; }

inline void* CSimpleList::GetHead() const
{ return m_pHead; }

inline void* CSimpleList::GetNext(void* preElement) const
{ return *GetNextPtr(preElement); }

inline void** CSimpleList::GetNextPtr(void* p) const
{ return (void**)((BYTE*)p + m_nNextOffset); }


template<class TYPE>
class CTypedSimpleList : public CSimpleList
{
public:
	CTypedSimpleList(int nNextOffset = 0) 
		: CSimpleList(nNextOffset) { }
	void AddHead(TYPE p) 
		{ CSimpleList::AddHead((void*)p); }
	TYPE GetHead()
		{ return (TYPE)CSimpleList::GetHead(); }
	TYPE GetNext(TYPE p)
		{ return (TYPE)CSimpleList::GetNext(p); }
	BOOL Remove(TYPE p)
		{ return CSimpleList::Remove(p); }
	operator TYPE()
		{ return (TYPE)CSimpleList::GetHead(); }
};



// CNoTrackObject
class CNoTrackObject
{
public:
	void* operator new(size_t nSize);
	void operator delete(void*);
	virtual ~CNoTrackObject() { }
};

/
// CThreadSlotData - manage our own thread local storage

// warning C4291: no matching operator delete found
#pragma warning(disable : 4291) 

struct CSlotData;
struct CThreadData;

class CThreadSlotData
{
public:
	CThreadSlotData();

// Interface functions (Operations) provided to users
	int AllocSlot();	
	void FreeSlot(int nSlot); 
	void* GetThreadValue(int nSlot); 
	void SetValue(int nSlot, void* pValue);
	void DeleteValues(HINSTANCE hInst, BOOL bAll = FALSE);

// Class implementation code
	DWORD m_tlsIndex;	// Used to access thread local storage provided by the system

	int m_nAlloc;		//  m_ The size of the array pointed to by pslotdata
	int m_nRover;		// The value set to quickly find an empty slot
	int m_nMax;		// The size of the array pointed to by pData in the CThreadData structure
	CSlotData* m_pSlotData;	// The first address of the global array that identifies the status of each slot
	CTypedSimpleList<CThreadData*> m_list;	// List of CThreadData structures
	CRITICAL_SECTION m_cs;

	void* operator new(size_t, void* p)
			{ return p; }
	void DeleteValues(CThreadData* pData, HINSTANCE hInst);
	~CThreadSlotData();
};


///

class CThreadLocalObject
{
public:
// Attribute member (Attributes), which is used to obtain the pointer saved in the variable local to the thread
	CNoTrackObject* GetData(CNoTrackObject* (*pfnCreateObject)());
	CNoTrackObject* GetDataNA();

// Implementation
	DWORD m_nSlot;
	~CThreadLocalObject();
};


template<class TYPE>
class CThreadLocal : public CThreadLocalObject
{
// Attributes
public:
	TYPE* GetData()
	{
		TYPE* pData = (TYPE*)CThreadLocalObject::GetData(&CreateObject);
		return pData;
	}
	TYPE* GetDataNA()
	{
		TYPE* pData = (TYPE*)CThreadLocalObject::GetDataNA();
		return pData;
	}
	operator TYPE*()
		{ return GetData(); }
	TYPE* operator->()
		{ return GetData(); }

// Implementation
public:
	static CNoTrackObject* CreateObject()
		{ return new TYPE; }
};


#define THREAD_LOCAL(class_name, ident_name) \
	CThreadLocal<class_name> ident_name;
#define EXTERN_THREAD_LOCAL(class_name, ident_name) \
	extern THREAD_LOCAL(class_name, ident_name)


#endif // __AFXTLS_H__

AFXTLS.CPP

//AFXTLS.CPP file
#include "_AFXTLS_.H"

void CSimpleList::AddHead(void* p)
{
	*GetNextPtr(p) = m_pHead;
	m_pHead = p;
}

BOOL CSimpleList::Remove(void* p)
{
	if(p == NULL)	//Check parameters
		return FALSE;
	
	BOOL bResult = FALSE; //Suppose the removal fails
	if(p == m_pHead)
	{
	//To remove a header element
		m_pHead = *GetNextPtr(p);
		bResult = TRUE;
	}
	else
	{
		//An attempt was made to find an element to remove in a table
		void* pTest = m_pHead;
		while(pTest != NULL && *GetNextPtr(pTest) != p)
			pTest = *GetNextPtr(pTest);

		//If found, remove the element
		if(pTest != NULL)
		{
			*GetNextPtr(pTest) = *GetNextPtr(p);
			bResult = TRUE;
		}
	}
	return bResult;
}

//-------------------CThreadSlotData class----------------------//
BYTE __afxThreadData[sizeof(CThreadSlotData)];	//For the following_ The afxThreadData variable provides memory
CThreadSlotData* _afxThreadData; //Define global variables_ afxThreadData to allocate space for global variables

struct CSlotData
{
	DWORD dwFlags;	//Slot usage flag (assigned / unassigned)
	HINSTANCE hInst;//The module handle that occupies this slot
};

struct CThreadData : public CNoTrackObject
{
	CThreadData* pNext; //This member is used by the CSimpleList class
	int nCount;	    //Number of array elements
	LPVOID* pData;      //First address of array
};

#define SLOT_USED 0x01 		// When the value of dwFlags member in CSlotData structure is 0x01, it indicates that the slot has been used

CThreadSlotData::CThreadSlotData()
{
	m_list.Construct(offsetof(CThreadData, pNext)); //Initializes the CTypedSimpleList object

	m_nMax = 0;
	m_nAlloc = 0;
	m_nRover = 1;	//We assume that Slot1 has not been allocated (the first slot (Slot0) is always reserved and not used)
	m_pSlotData = NULL;

	m_tlsIndex = ::TlsAlloc();	//Use the system's TLS to request an index
	::InitializeCriticalSection(&m_cs);	//Initialize key segment variables
}

int CThreadSlotData::AllocSlot()
{
	::EnterCriticalSection(&m_cs);	//Enter critical zone (also known as critical section)
	int nAlloc = m_nAlloc;
	int nSlot = m_nRover;

	if(nSlot >= nAlloc || m_pSlotData[nSlot].dwFlags & SLOT_USED)
	{
		//Search m_pSlotData to find an empty SLOT (SLOT)
		for(nSlot = 1; nSlot < nAlloc && m_pSlotData[nSlot].dwFlags & SLOT_USED; nSlot ++) ;

		//If there is no empty slot, apply for more space
		if(nSlot >= nAlloc)
		{
			//Increase the size of the global array and allocate or reallocate memory to create new slots
			int nNewAlloc = nAlloc + 32;

			HGLOBAL hSlotData;
			if(m_pSlotData == NULL)	//First use
			{
				hSlotData = ::GlobalAlloc(GMEM_MOVEABLE, nNewAlloc*sizeof(CSlotData));
			}
			else
			{
				hSlotData = ::GlobalHandle(m_pSlotData);
				::GlobalUnlock(hSlotData);
				hSlotData = ::GlobalReAlloc(hSlotData, 
					nNewAlloc*sizeof(CSlotData), GMEM_MOVEABLE);
			}
			CSlotData* pSlotData = (CSlotData*)::GlobalLock(hSlotData);
	
			//Initialize the newly requested space to 0
			memset(pSlotData + m_nAlloc, 0, (nNewAlloc - nAlloc)*sizeof(CSlotData));
			m_nAlloc = nNewAlloc;
			m_pSlotData = pSlotData;
		}
	}

	//Adjustment M_ The value of nmax to allocate memory for the private data of each thread
	if(nSlot >= m_nMax)
		m_nMax = nSlot + 1;

	m_pSlotData[nSlot].dwFlags |= SLOT_USED;
	//Update M_ Value of nlrover (we assume that the next slot is not used)
	m_nRover = nSlot + 1;

	::LeaveCriticalSection(&m_cs);
	return nSlot; //The returned slot number can be used by freeslot, getthreadvalue and setValue functions
}

void CThreadSlotData::FreeSlot(int nSlot)
{
	::EnterCriticalSection(&m_cs);	

	//Delete data from all threads
	CThreadData* pData = m_list;
	while(pData != NULL)
	{
		if(nSlot < pData->nCount)
		{
			delete (CNoTrackObject*)pData->pData[nSlot];
			pData->pData[nSlot] = NULL;
		}
		pData = pData->pNext;
	}

	//Identify this slot number as unused
	m_pSlotData[nSlot].dwFlags &= ~SLOT_USED;
	::LeaveCriticalSection(&m_cs);
}

inline void* CThreadSlotData::GetThreadValue(int nSlot)
{
	CThreadData* pData = (CThreadData*)::TlsGetValue(m_tlsIndex);
	if(pData == NULL || nSlot >= pData->nCount)
		return NULL;
	return pData->pData[nSlot];
}

void CThreadSlotData::SetValue(int nSlot, void* pValue)
{
	//The private storage space we arranged for threads is obtained through TLS index
	CThreadData* pData = (CThreadData*)::TlsGetValue(m_tlsIndex);

	//Request memory space for thread private data
	if((pData == NULL || nSlot >= pData->nCount) && pValue != NULL)
	{
		//The value of pData is null, indicating that the thread accesses the thread private data for the first time
		if(pData == NULL)
		{
			pData = new CThreadData;
			pData->nCount = 0;
			pData->pData = NULL;

			//Add the address of the newly requested memory to the global list
			::EnterCriticalSection(&m_cs);
			m_list.AddHead(pData);
			::LeaveCriticalSection(&m_cs);
		}

		//Pdata - > pdata points to the real thread private data. The following code increases the space occupied by private data to M_ Size specified by nmax
		if(pData->pData == NULL)
			pData->pData = (void**)::GlobalAlloc(LMEM_FIXED, m_nMax*sizeof(LPVOID));
		else
			pData->pData = (void**)::GlobalReAlloc(pData->pData, m_nMax*sizeof(LPVOID), LMEM_MOVEABLE);
		
		//The newly requested memory is initially set to 0
		memset(pData->pData + pData->nCount, 0, 
			(m_nMax - pData->nCount) * sizeof(LPVOID));
		pData->nCount = m_nMax;
		::TlsSetValue(m_tlsIndex, pData);
	}

	//Sets the value of thread private data
	pData->pData[nSlot] = pValue;
}

void CThreadSlotData::DeleteValues(HINSTANCE hInst, BOOL bAll)
{
	::EnterCriticalSection(&m_cs);
	if(!bAll)
	{
		//Only delete the space occupied by the thread local storage of the current thread
		CThreadData* pData = (CThreadData*)::TlsGetValue(m_tlsIndex);
		if(pData != NULL)
			DeleteValues(pData, hInst);
	}
	else
	{
		//Delete the space occupied by thread local storage of all threads
		CThreadData* pData = m_list.GetHead();
		while(pData != NULL)
		{
			CThreadData* pNextData = pData->pNext;
			DeleteValues(pData, hInst);
			pData = pNextData;
		}
	}
	::LeaveCriticalSection(&m_cs);
}

void CThreadSlotData::DeleteValues(CThreadData* pData, HINSTANCE hInst)
{
	//Release each element in the table
	BOOL bDelete = TRUE;
	for(int i=1; i<pData->nCount; i++)
	{
		if(hInst == NULL || m_pSlotData[i].hInst == hInst)
		{
			//hInst match, delete data
			delete (CNoTrackObject*)pData->pData[i];
			pData->pData[i] = NULL;
		}
		else
		{
			//There are other modules in use. Do not delete data
			if(pData->pData[i] != NULL)
			bDelete = FALSE;
		}
	}

	if(bDelete)
	{
		//Remove from list
		::EnterCriticalSection(&m_cs);
		m_list.Remove(pData);
		::LeaveCriticalSection(&m_cs);
		::LocalFree(pData->pData);
		delete pData;

		//Clear TLS index to prevent reuse
		::TlsSetValue(m_tlsIndex, NULL);
	}
}

CThreadSlotData::~CThreadSlotData()
{
	CThreadData *pData = m_list;
	while(pData != NULL)
	{
		CThreadData* pDataNext = pData->pNext;
		DeleteValues(pData, NULL);
		pData = pData->pNext;
	}

	if(m_tlsIndex != (DWORD)-1)
		::TlsFree(m_tlsIndex);

	if(m_pSlotData != NULL)
	{
		HGLOBAL hSlotData = ::GlobalHandle(m_pSlotData);
		::GlobalUnlock(hSlotData);
		::GlobalFree(m_pSlotData);
	}

	::DeleteCriticalSection(&m_cs);
}

//---------------------------------------

void* CNoTrackObject::operator new(size_t nSize)
{
	//Apply for a piece with GMEM_FIXED and GMEM_ Memory with zeroinit flag
	void* p = ::GlobalAlloc(GPTR, nSize);
	return p;
}

void CNoTrackObject::operator delete(void* p)
{
	if(p != NULL)
		::GlobalFree(p);
}

//----------------------------CThreadLocalObject class--------------------------------//

CNoTrackObject* CThreadLocalObject::GetData(CNoTrackObject* (*pfnCreateObject)())
{
	if(m_nSlot == 0)
	{
		if(_afxThreadData == NULL)
			_afxThreadData = new(__afxThreadData) CThreadSlotData;
		m_nSlot = _afxThreadData->AllocSlot();
	}
 
	CNoTrackObject* pValue = (CNoTrackObject*)_afxThreadData->GetThreadValue(m_nSlot);
	if(pValue == NULL)
	{
		//Create a data item
		pValue = (*pfnCreateObject)();

		//Use thread private data to save newly created objects
		_afxThreadData->SetValue(m_nSlot, pValue);	
	}
	
	return pValue;
}

CNoTrackObject* CThreadLocalObject::GetDataNA()
{
	if(m_nSlot == 0 || _afxThreadData == 0)
		return NULL;
	return (CNoTrackObject*)_afxThreadData->GetThreadValue(m_nSlot);
}

CThreadLocalObject::~CThreadLocalObject()
{
	if(m_nSlot != 0 && _afxThreadData != NULL)
		_afxThreadData->FreeSlot(m_nSlot);
	m_nSlot = 0;
}

Tags: C++ Back-end

Posted on Thu, 18 Nov 2021 06:01:42 -0500 by osnewbie2004