线程局部存储的Win32实现
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程局部存储的Win32实现相关的知识,希望对你有一定的参考价值。
参考技术A 每个线程创建时系统给它分配一个LPVOID指针的数组(叫做TLS索引数组),这个数组从C编程角度是隐藏着的不能直接访问(实际上该数组地址写入了线程信息块 thread information block,缩写TIB或TEB),需要通过一些Kernel32 API函数调用访问。在进程内部创建、并发执行的各个线程,可以看作是执行相同动作(代码是一样的),但输入的数据不同,所以输出的结果数据也不同。因此各个线程使用的数据结构是相同的,只是有些变量是被所有的线程共享访问,为进程全局变量;另外一些变量是由每个线程独享访问,即线程局部存储。而每个线程局部存储的地址需要存入该线程的TLS索引数组。
举例说明:设每个线程都要使用线程私有的一个浮点型变量fvalue与一个长度为512个字节的缓冲区buf。需要在启动这些线程前,在主进程中先为fvalue与buf两个线程局部存储变量在TLS索引数组申请两个条目,假设为fvalue申请到第3号条目,为buf申请到第5号条目。也就是说,在任何一个线程内访问该线程私有的fvalue,需要查询该线程自己的TLS索引数组,其第3号条目存放的就是fvalue的地址。当然,启动各个线程后还需要为线程私有的fvalue与buf从堆中申请到存储空间,然后把fvalue与buf的地址登记入该线程的TLS索引数组的对应的第3号、第5号条目中,之后才能在该线程各处使用线程私有的fvalue与buf。
第一步,在主进程内调用TlsAlloc()函数,从将要启动的每个线程的TLS索引数组中预定一个条目(slot),并返回该条目的序号:
DWORD global_dwTLS_fvalue = TLSAlloc();
注意,此步之后,变量( global_dwTLS_fvalue )保存的是分配得到的TLS索引数组的某个条目的序号,例如值为3。编程者在写这个程序代码时规定了这个变量( global_dwTLS_fvalue )保存了线程局部存储fvalue在每个线程的TLS索引数组的对应条目的序号。变量( global_dwTLS_fvalue )是普通的全局变量,各个线程随后只需要读取它的值。类似的,另外一个线程局部存储buf变量也需要定义一个变量( global_dwTLS_buf )并用TLSAlloc()初始化。
第二步,在每个进程执行的一开头,从堆中动态分配一块内存区域(使用LocalAlloc()函数调用)
void* p_fvalue = LocalAlloc(LPTR,sizeof(float));
然后使用TlsSetValue()函数调用,把这块内存区域的地址存入TLS索引数组相应的条目中:
TlsSetValue( global_dwTLS_fvalue, p_fvalue);
第三步,在每个线程的任意执行位置,都可以通过该线程私有的TLS索引数组的相应条目,使用TlsGetValue()函数得到上一步的那块内存区域的地址,然后就可以对该内存区域做读写操作了。这就实现了在一个线程内部处处可访问的线程局部存储。
LPVOID lpvData = TlsGetValue(global_dwTLS_fvalue);
*lpvData = (float) 3.1416; //应用该线程局部存储
最后,如果不再需要上述线程局部静态变量,要动态释放掉这块内存区域(使用LocalFree()函数),这一般在线程即将结束时清理线程占用的各项资源时释放。然后,主进程从TLS索引数组中放弃对应的条目的占用(使用TlsFree()函数)。
LocalFree((HLOCAL) p_fvalue );
TlsFree(global_dwTLS_fvalue); 直接声明这个变量是各个线程有自己拷贝的线程局部静态变量:
__declspec( thread ) int var_name;
但在Vista与Server 2008之前的操作系统,仅限于在应用程序的主进程(.exe)以及与主进程一起装入内存的动态连接库(.dll),才能正常装入本方法所声明的线程静态存储。
设计自己的线程局部存储
参考资料 王艳平 《Windows程序设计》
#pragma once #include <windows.h> #include <stddef.h> #include<iostream> using namespace std; class CNoTrackObject; class CSimpleList//将每个线程的私有数据的首地址串联起来 { public: CSimpleList(int NextOffset = 0); void Construct(int NextOffset); // 提供给用户的接口函数(Operations),用于添加、删除和遍历节点 BOOL IsEmpty() const; void AddHead(void* p); void RemoveAll(); void* GetHead() const; void* GetNext(void* p) const; BOOL Remove(void* p); // 为实现接口函数所需的成员(Implementation) void* m_pHead; // 链表中第一个元素的地址 size_t m_NextOffset; // 数据结构中pNext成员的偏移量 void** GetNextPtr(void* p) const; }; // 类的内联函数 inline CSimpleList::CSimpleList(int nNextOffset) { m_pHead = NULL; m_NextOffset = nNextOffset; } inline void CSimpleList::Construct(int NextOffset) { m_NextOffset = NextOffset; } 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_NextOffset); } //实现自动类型转换 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(); } }; //如何为线程私有数据分配内存 //重写operator new delete //让所有线程的私有哦数据使用的结构的都从此类继承 //////////////////////////////////////////////// // CNoTrackObject class CNoTrackObject { public: void* operator new(size_t nSize); void operator delete(void*); virtual ~CNoTrackObject() { } }; ///////////////////////////////////////////////// // CThreadSlotData - 管理我们自己的线程局部存储 // warning C4291: no matching operator delete found #pragma warning(disable : 4291) //不显示警告 struct CSlotData; struct CThreadData;//成员指针指向真正的线程私有数据,把pData指向的空间分成多个槽//这些槽(slot)组成PVOID类型的数组,每一个元素保存一个指针,即线程私有数据指针, //它指向堆中分配的内存 class CThreadSlotData { public: CThreadSlotData(); // 提供给用户的接口函数(Operations) int AllocSlot(); void FreeSlot(int nSlot); void* GetThreadValue(int nSlot); void SetValue(int nSlot, void* pValue); void DeleteValues(HINSTANCE hInst, BOOL bAll = FALSE); // 类的实现代码(Implementations) DWORD m_TLSIndex; // 用来访问系统提供的线程局部存储 int m_nAlloc; // m_pSlotData所指向数组的大小 int m_nRover; // 为了快速找到一个空闲的槽而设定的值,总是假设当前分配槽的下一个槽未被使用 int m_nMax; // 迄今为止占用槽的最大数目 CSlotData* m_pSlotData; // 标识每个槽状态和句柄的全局数组的首地址 CTypedSimpleList<CThreadData*> m_list; // CThreadData结构的列表 CRITICAL_SECTION m_cs; void* operator new(size_t, void* p)//只返回参数中的指针作为对象的首地址 { return p; } void DeleteValues(CThreadData* pData, HINSTANCE hInst); ~CThreadSlotData(); }; struct CSlotData { DWORD dwFlags; // 槽的使用标志(被分配/未被分配) HINSTANCE hInst;// 占用此槽的模块句柄 }; struct CThreadData : public CNoTrackObject { CThreadData* pNext; // CSimpleList类要使用此成员 int nCount; // 数组元素的个数 LPVOID* pData; // 数组的首地址 }; /////////////////////////////////////////////// /////////////////////////////////////////////// class CThreadLocalObject { public: // 属性成员(Attributes),用于取得保存在线程局部的变量中的指针 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)
// MYTLS.cpp : 定义控制台应用程序的入口点。 // #include "MYTLS.h" BYTE __MYThreadData[sizeof(CThreadSlotData)]; // 为下面的MYThreadData变量提供内存 CThreadSlotData* _MYThreadData; // 定义全局变量MYThreadData来为全局变量分配空间 #define SLOT_USED 0x01 // CSlotData结构中dwFlags成员的值为0x01时表示该槽已被使用 void CSimpleList::AddHead(void* p) { *GetNextPtr(p) = m_pHead;//p的pNext指向原表头 m_pHead = p; } BOOL CSimpleList::Remove(void* p) { if(p == NULL) return FALSE; BOOL IsResult = FALSE; // 假设移除失败 if(p == m_pHead) { // 要移除头元素 m_pHead = *GetNextPtr(p); IsResult = TRUE; } else { // 试图在表中查找要移除的元素 void* pTest = m_pHead; while(pTest != NULL && *GetNextPtr(pTest) != p) pTest = *GetNextPtr(pTest); // 如果找到,就将元素移除 if(pTest != NULL) { *GetNextPtr(pTest) = *GetNextPtr(p); IsResult = TRUE; } } return IsResult; } //--------------------------------------- void* CNoTrackObject::operator new(size_t nSize) { //GHND 申请一块带有GMEM_MOVEABLE和GMEM_ZEROINIT标志的内存 //GMEM_MOVEABLE 可移动 返回内存对象的句柄 用GlobalLock将句柄转换为指针 //GMEM_FIXED 固定 返回内存指针 //GMEMZEROINIT 初始化为0 // GPTR申请一块带有GMEM_FIXED和GMEM_ZEROINIT标志的内存 void* p = ::GlobalAlloc(GPTR, nSize); return p; } void CNoTrackObject::operator delete(void* p) { if(p != NULL) ::GlobalFree(p); } //-------------------CThreadSlotData类----------------------// CThreadSlotData::CThreadSlotData() { //进来后先进构造函数 初始化部分变量 // m_list.Construct(offsetof(CThreadData, pNext)); // 初始化CTypedSimpleList对象 计算得到偏移量赋给m_NextOffset m_nMax = 0; m_nAlloc = 0; m_nRover = 1; // 我们假定Slot1还未被分配(第一个槽(Slot0)总是保留下来不被使用) m_pSlotData = NULL; m_TLSIndex =::TlsAlloc(); // 使用系统的TLS申请一个索引 ::InitializeCriticalSection(&m_cs); // 初始化关键段变量 } int CThreadSlotData::AllocSlot() { ::EnterCriticalSection(&m_cs); int nAlloc = m_nAlloc;//开始0 int nSlot = m_nRover;// 1 第一个槽保留 if(nSlot >= nAlloc || m_pSlotData[nSlot].dwFlags & SLOT_USED)//当快速查找的槽超出分配或者这个位置已经被使用 { // 搜索m_pSlotData,查找空槽(SLOT) for(nSlot = 1; nSlot < nAlloc && m_pSlotData[nSlot].dwFlags & SLOT_USED; nSlot ++) ; // 如果不存在空槽,申请更多的空间 if(nSlot >= nAlloc) { // 增加全局数组的大小,分配或再分配内存以创建新槽 int nNewAlloc = nAlloc + 32; HGLOBAL hSlotData; if(m_pSlotData == NULL) // 第一次使用 { hSlotData = GlobalAlloc(GMEM_MOVEABLE, nNewAlloc*sizeof(CSlotData));//申请了一组可移动内存 } else { hSlotData = GlobalHandle(m_pSlotData); GlobalUnlock(hSlotData);//Decrements the lock count associated with a memory object that was allocated with GMEM_MOVEABLE. This function has no effect on memory objects allocated with GMEM_FIXED. hSlotData = GlobalReAlloc(hSlotData, nNewAlloc*sizeof(CSlotData), GMEM_MOVEABLE); } CSlotData* pSlotData = (CSlotData*)GlobalLock(hSlotData);//得到地址 // 将新申请的空间初始化为0 memset(pSlotData + m_nAlloc, 0, (nNewAlloc - nAlloc)*sizeof(CSlotData)); m_nAlloc = nNewAlloc; m_pSlotData = pSlotData; } } // 调整m_nMax的值,以便为各线程的私有数据分配内存 if(nSlot >= m_nMax) m_nMax = nSlot + 1; m_pSlotData[nSlot].dwFlags |= SLOT_USED; // 更新m_nRover的值(我们假设下一个槽未被使用) m_nRover = nSlot + 1; ::LeaveCriticalSection(&m_cs); return nSlot; // 返回的槽号可以被FreeSlot, GetThreadValue, SetValue函数使用了 } void CThreadSlotData::FreeSlot(int nSlot)//所有线程的该号槽都会被清除 { ::EnterCriticalSection(&m_cs); // 删除所有线程中的数据 CThreadData* pData = m_list; while(pData != NULL) { if(nSlot < pData->nCount) { delete (CNoTrackObject*)pData->pData[nSlot];//用的是CNoTrackObject重载的 pData->pData[nSlot] = NULL; } pData = pData->pNext; } // 将此槽号标识为未被使用 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) { // 通过TLS索引得到我们为线程安排的私有存储空间 CThreadData* v1 = (CThreadData*)TlsGetValue(m_TLSIndex);//利用系统的TLS // 为线程私有数据申请内存空间 if((v1 == NULL || nSlot >= v1->nCount) && pValue != NULL) { // pData的值为空,表示该线程第一次访问线程私有数据 if(v1 == NULL)//初始化v1 { v1 = new CThreadData; v1->nCount = 0; v1->pData = NULL; // 将新申请的内存的地址添加到全局列表中 EnterCriticalSection(&m_cs); m_list.AddHead(v1);//设置各个数据头部CtreadData结构的pNext值 LeaveCriticalSection(&m_cs); } // v1->pData指向真正的线程私有数据,下面的代码将私有数据占用的空间增长到m_nMax指定的大小 if(v1->pData == NULL) v1->pData = (void**)::GlobalAlloc(LMEM_FIXED, m_nMax*sizeof(LPVOID)); else v1->pData = (void**)::GlobalReAlloc(v1->pData, m_nMax*sizeof(LPVOID), LMEM_MOVEABLE); // 将新申请的内存初始话为0 memset(v1->pData + (v1->nCount)* sizeof(LPVOID), 0, (m_nMax - v1->nCount) * sizeof(LPVOID));//源代码此处为v1->pData + v1->nCount ,但我认为是v1->pData + (v1->nCount)* sizeof(LPVOID) v1->nCount = m_nMax;//类似偏移 TlsSetValue(m_TLSIndex, v1); } // 设置线程私有数据的值 v1->pData[nSlot] = pValue;//pValue是个地址 } void CThreadSlotData::DeleteValues(HINSTANCE hInst, BOOL IsAll) { EnterCriticalSection(&m_cs); if(!IsAll) { // 仅仅删除当前线程的线程局部存储占用的空间 CThreadData* pData = (CThreadData*)TlsGetValue(m_TLSIndex); if(pData != NULL) DeleteValues(pData, hInst); } else { // 删除所有线程的线程局部存储占用的空间 CThreadData* pData = m_list.GetHead(); while(pData != NULL) { CThreadData* pNextData = pData->pNext; DeleteValues(pData, hInst); pData = pNextData; } } LeaveCriticalSection(&m_cs); } void CThreadSlotData::DeleteValues(CThreadData* v1, HINSTANCE hInst) { // 释放表中的每一个元素 BOOL bDelete = TRUE; for(int i=1; i<v1->nCount; i++) { if(hInst == NULL || m_pSlotData[i].hInst == hInst) { // hInst匹配,删除数据 delete (CNoTrackObject*)v1->pData[i]; v1->pData[i] = NULL; } else { // 还有其它模块在使用,不要删除数据 if(v1->pData[i] != NULL) bDelete = FALSE;//只要有其他槽还在使用就将bDelete置为FALSE } } if(bDelete)//当还有有其他槽还在使用就将bDelete置为FALSE,不进 { // 从列表中移除 EnterCriticalSection(&m_cs); m_list.Remove(v1); LeaveCriticalSection(&m_cs); LocalFree(v1->pData); delete v1; // 清除TLS索引,防止重用 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); } //----------------------------CThreadLocalObject 类--------------------------------// CNoTrackObject* CThreadLocalObject::GetData(CNoTrackObject* (*pfnCreateObject)()) { if(m_nSlot == 0)//变量没分配槽号 { if(_MYThreadData == NULL) _MYThreadData = new(__MYThreadData)CThreadSlotData;//此处new重构 只返回参数中的指针作为对象的首地址 BYTE __MYThreadData[sizeof(CThreadSlotData)]; m_nSlot = _MYThreadData->AllocSlot();//变为1 } CNoTrackObject* pValue = (CNoTrackObject*)_MYThreadData->GetThreadValue(m_nSlot);//当槽里没有东西时返回null if(pValue == NULL) { // 创建一个数据项 pValue = (*pfnCreateObject)(); // 使用线程私有数据保存新创建的对象 _MYThreadData->SetValue(m_nSlot, pValue); } return pValue; } CNoTrackObject* CThreadLocalObject::GetDataNA() { if(m_nSlot == 0 || _MYThreadData == 0) return NULL; return (CNoTrackObject*)_MYThreadData->GetThreadValue(m_nSlot); } CThreadLocalObject::~CThreadLocalObject() { if(m_nSlot != 0 && _MYThreadData != NULL) _MYThreadData->FreeSlot(m_nSlot); m_nSlot = 0; } //------------------------------------------
#include"MYTLS.h" #include <process.h> #include <iostream> using namespace std; struct CMyThreadData : public CNoTrackObject { int nSomeData; }; CThreadLocal<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; // 启动十个线程,将i做为线程函数的参数传过去 for(int i=0; i<1; i++) h[i] = (HANDLE) _beginthreadex(NULL, 0, ThreadFunc, (void*)(i+1), 0, &uID);//i作为参数传进去 WaitForMultipleObjects(1, h, TRUE, INFINITE); for(int i=0; i<1; i++) CloseHandle(h[i]); getchar(); } void ShowData() { int nData = g_myThreadData->nSomeData; printf(" Thread ID: %-5d, nSomeData = %d \n", GetCurrentThreadId(), nData); }
以上是关于线程局部存储的Win32实现的主要内容,如果未能解决你的问题,请参考以下文章