AfxGetThreadState 与 _AFX_THREAD_STATE 剖析

来源:互联网 发布:最新淘宝外卖粮票领取 编辑:程序博客网 时间:2024/05/18 21:49

    在分析MFC的源码中经常看到 AfxGetThreadState这个函数,它用来返回当前线程的一些信息,其代码主要在 afxstat_.h  afxtls_.h 及它们对应的 afxstate.cpp 和afxstl.cpp中,AfxGetTheradState函数的定义位于 afxstate.cpp中,定义如下:

_AFX_THREAD_STATE* AFXAPI AfxGetThreadState(){_AFX_THREAD_STATE *pState =_afxThreadState.GetData();  //_afxThreadState是一个全局模板类对象ENSURE(pState != NULL); return pState;}

_AFX_THREAD_STATE  就是MFC关于线程状态的类  声明在afxstat_.h中,而_afxThreadState 是一个全局模板对象位于定义在afxstate.cpp中,先来看下这个类的声明:

class AFX_NOVTABLE CNoTrackObject{public:void* PASCAL operator new(size_t nSize);void PASCAL operator delete(void*);#if defined(_DEBUG) && !defined(_AFX_NO_DEBUG_CRT)void* PASCAL operator new(size_t nSize, LPCSTR, int);void PASCAL operator delete(void* pObject, LPCSTR, int);#endif    virtual ~CNoTrackObject() {};};


class _AFX_THREAD_STATE : public CNoTrackObject    //虚基类{public:_AFX_THREAD_STATE();virtual ~_AFX_THREAD_STATE();// override for m_pModuleState in _AFX_APP_STATEAFX_MODULE_STATE* m_pModuleState;            //****  AFX_MODULE_STATE 重要的一个类,保存了当前线程执行代码所在模块的信息。 ****//AFX_MODULE_STATE* m_pPrevModuleState;// memory safety pool for temp mapsvoid* m_pSafetyPoolBuffer;    // current buffer// thread local exception contextAFX_EXCEPTION_CONTEXT m_exceptionContext;// CWnd create, gray dialog hook, and other hook dataCWnd* m_pWndInit;   //CWND类在创建每个窗口前 用这个指针标记要被HOOK的窗口所对应的类的指针,参见 AfxHookWindowCreateCWnd* m_pAlternateWndInit;      // special case commdlg hooking     通用对话框HOOK的标记指针DWORD m_dwPropStyle;DWORD m_dwPropExStyle;          //属性页切换类型保存HWND m_hWndInit;HHOOK m_hHookOldCbtFilter;          //保存CBT钩子函数的句柄,_AfxCbtFilterHook 在窗口创建后钩子函数将窗口过程设置为AfxWndProcHHOOK m_hHookOldMsgFilter;// message pump for RunMSG m_msgCur;                   // current messageCPoint m_ptCursorLast;          // last mouse positionUINT m_nMsgLast;                // last mouse message  //三个用于CWinApp的消息循环#ifdef _DEBUGint m_nDisablePumpCount; // Diagnostic trap to detect illegal re-entrancy#endif// other CWnd modal dataMSG m_lastSentMsg;              // see CWnd::WindowProcHWND m_hTrackingWindow;         // see CWnd::TrackPopupMenuHMENU m_hTrackingMenu;TCHAR m_szTempClassName[_AFX_TEMP_CLASS_NAME_SIZE];    // see AfxRegisterWndClassHWND m_hLockoutNotifyWindow;    // see CWnd::OnCommandBOOL m_bInMsgFilter;                                         //以上为每个线程的MFC窗口类的消息路由提供支持和// other framework modal dataCView* m_pRoutingView;          // see CCmdTarget::GetRoutingView   CPushRoutingView* m_pPushRoutingView;CFrameWnd* m_pRoutingFrame;     // see CCmdTarget::GetRoutingFrame   CPushRoutingFrame* m_pPushRoutingFrame;// MFC/DB thread-local dataBOOL m_bWaitForDataSource;// OLE control thread-local dataCWnd* m_pWndPark;       // "parking space" windowlong m_nCtrlRef;        // reference count on parking windowBOOL m_bNeedTerm;       // TRUE if OleUninitialize needs to be called};

这个类确切的说应该更像是个结构体,并没有给类定义任何功能性的接口函数,主要用来为MFC的消息路由机制提供全局变量支持,其中两个AFX_MODULE_STATE指针 在编写MFC的DLL文件的时候很重要。

回过头来看 _afxThreadState 这个全局对象,它在afxstate.cpp 中是这样定义的

THREAD_LOCAL(_AFX_THREAD_STATE, _afxThreadState)

THREAD_LOCAL宏为

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

这里_afxThreadState  是一个CThreadLocal<_AFX_THREAD_STATE> 模板类的实例对象。

template<class TYPE>    //声明在afxstl_.h文件中class CThreadLocal : public CThreadLocalObject{// Attributespublic:AFX_INLINE TYPE* GetData(){TYPE* pData = (TYPE*)CThreadLocalObject::GetData(&CreateObject);ENSURE(pData != NULL);return pData;}AFX_INLINE TYPE* GetDataNA(){TYPE* pData = (TYPE*)CThreadLocalObject::GetDataNA();return pData;}AFX_INLINE operator TYPE*(){ return GetData(); }AFX_INLINE TYPE* operator->(){ return GetData(); }// Implementationpublic:static CNoTrackObject* AFXAPI CreateObject(){ return new TYPE; }
这个模板类只是对其基类  CThreadLocalObject 的简单扩展,再看CThreadLocalObject 的声明,

class AFX_NOVTABLE CThreadLocalObject{public:// AttributesCNoTrackObject* GetData(CNoTrackObject* (AFXAPI* pfnCreateObject)());CNoTrackObject* GetDataNA();// Implementationint m_nSlot;  ~CThreadLocalObject();};

在调用AfxGetThreadState()中 
_AFX_THREAD_STATE *pState =_afxThreadState.GetData();    //这个CThreadLocal<_AFX_THREAD_STATE> 类型的全局对象调用了CThreadLocalObject的GetData函数,这个函数
的实现如下:

CNoTrackObject* CThreadLocalObject::GetData(CNoTrackObject* (AFXAPI* pfnCreateObject)())  {    ENSURE(pfnCreateObject);if (m_nSlot == 0){if (_afxThreadData == NULL)     //全局指针变量 指向 CThreadSlotData 类型的对象 在第一次调用的时候 在堆中创建对象赋给指针{_afxThreadData = new(__afxThreadData) CThreadSlotData; //ENSURE(_afxThreadData != NULL);}m_nSlot = _afxThreadData->AllocSlot();  //返回申请到的槽号,ENSURE(m_nSlot != 0);}CNoTrackObject* pValue = static_cast<CNoTrackObject*>(_afxThreadData->GetThreadValue(m_nSlot));if (pValue == NULL)  //CThreadSlotData对象以线程本地存储机制来存放数据,同样的槽号每个线程中对应不同的数据指针{// allocate zero-init objectpValue = (*pfnCreateObject)();  //如果m_nSolt槽号获取到的数据指针无效,在堆中创建一个AFX_THREAD_STATE对象,pfnCreateObject函数指针对应
NEW AFX_THREAD_STATE
// set tls data to newly created object_afxThreadData->SetValue(m_nSlot, pValue);  //将创建的AFX_THREAD_STATE对象用线程局部储存,保存。ASSERT(_afxThreadData->GetThreadValue(m_nSlot) == pValue);}return pValue;



_afxThreadData是一个全局对象指针,__afxThreadData (注意多了一个下划线)则是一个全局数组,

_afxThreadData = new(__afxThreadData) CThreadSlotData; // 在全局静态空间中创建了一个CThreadSlotData 对象

他们的定义在afxtls.cpp中:

// global _afxThreadData used to allocate thread local indexesBYTE __afxThreadData[sizeof(CThreadSlotData)];CThreadSlotData* _afxThreadData;


CThreadSlotData  使用了线程本地储存的机制来存放每个线程的 AFX_THREAD_STATE 对象,来看下它的声明和实现

struct CThreadData : public CNoTrackObject{CThreadData* pNext; // required to be member of CSimpleListint nCount;         // current size of pDataLPVOID* pData;      // actual thread local data (indexed by nSlot)};struct CSlotData{DWORD dwFlags;      // slot flags (allocated/not allocated)HINSTANCE hInst;    // module which owns this slot};


class CThreadSlotData{public:CThreadSlotData();// Operationsint AllocSlot();void FreeSlot(int nSlot);void SetValue(int nSlot, void* pValue);// delete all values in process/threadvoid DeleteValues(HINSTANCE hInst, BOOL bAll = FALSE);// assign instance handle to just constructed slotsvoid AssignInstance(HINSTANCE hInst);// ImplementationDWORD m_tlsIndex;   // used to access system thread-local storage  线程本地储存的索引号int m_nAlloc;       // number of slots allocated (in UINTs)         int m_nRover;       // (optimization) for quick finding of free slotsint m_nMax;         // size of slot table below (in bits)CSlotData* m_pSlotData; // state of each slot (allocated or not)CTypedSimpleList<CThreadData*> m_list;  // list of CThreadData structuresCRITICAL_SECTION m_sect;void* GetThreadValue(int nSlot); // special version for threads only!void* PASCAL operator new(size_t, void* p){ return p; }void DeleteValues(CThreadData* pData, HINSTANCE hInst);~CThreadSlotData();

int CThreadSlotData::AllocSlot(){EnterCriticalSection(&m_sect);int nAlloc = m_nAlloc;int nSlot = m_nRover;if (nSlot >= nAlloc || (m_pSlotData[nSlot].dwFlags & SLOT_USED)) //如果没有给m_pSlotData分配过 或者分配的内存不够的话{// search for first free slot, starting at beginningfor (nSlot = 1;nSlot < nAlloc && (m_pSlotData[nSlot].dwFlags & SLOT_USED); nSlot++);// if none found, need to allocate more spaceif (nSlot >= nAlloc){// realloc memory for the bit array and the slot memoryint nNewAlloc = m_nAlloc+32;  //分配粒度32HGLOBAL hSlotData;if (m_pSlotData == NULL){hSlotData = GlobalAlloc(GMEM_MOVEABLE, static_cast<UINT>(::ATL::AtlMultiplyThrow(static_cast<UINT>(nNewAlloc),static_cast<UINT>(sizeof(CSlotData)))));
                                                                                     //nNewAlloc * sizeof(CSlotData)      }else{hSlotData = GlobalHandle(m_pSlotData);GlobalUnlock(hSlotData);hSlotData = GlobalReAlloc(hSlotData, static_cast<UINT>(::ATL::AtlMultiplyThrow(static_cast<UINT>(nNewAlloc),static_cast<UINT>(sizeof(CSlotData)))), GMEM_MOVEABLE|GMEM_SHARE);}if (hSlotData == NULL){if (m_pSlotData != NULL)GlobalLock(GlobalHandle(m_pSlotData));LeaveCriticalSection(&m_sect);AfxThrowMemoryException();}CSlotData* pSlotData = (CSlotData*)GlobalLock(hSlotData);// always zero initialize after successmemset(pSlotData+m_nAlloc, 0, (nNewAlloc-m_nAlloc)*sizeof(CSlotData));m_nAlloc = nNewAlloc;m_pSlotData = pSlotData;}}ASSERT(nSlot != 0); // first slot (0) is reserved// adjust m_nMax to largest slot ever allocatedif (nSlot >= m_nMax)m_nMax = nSlot+1;ASSERT(!(m_pSlotData[nSlot].dwFlags & SLOT_USED));m_pSlotData[nSlot].dwFlags |= SLOT_USED;               //将槽号为nSolt的CSlotData的标志设置为已使用// update m_nRover (likely place to find a free slot is next one)m_nRover = nSlot+1;LeaveCriticalSection(&m_sect);return nSlot;   // slot can be used for FreeSlot, GetValue, SetValue}
void CThreadSlotData::SetValue(int nSlot, void* pValue){EnterCriticalSection(&m_sect);  //临界区对象 防止多线程重入ASSERT(nSlot != 0 && nSlot < m_nMax);ASSERT(m_pSlotData != NULL);ASSERT(m_pSlotData[nSlot].dwFlags & SLOT_USED);if( nSlot <= 0 || nSlot >= m_nMax ) // check for retail builds.{LeaveCriticalSection(&m_sect);return;}CThreadData* pData = (CThreadData*)TlsGetValue(m_tlsIndex);   //使用线程本地储存 保证每个线程拥有自己的CThreadDate 结构在堆中
if (pData == NULL || nSlot >= pData->nCount && pValue != NULL){// if pData is NULL then this thread has not been visited yetif (pData == NULL)   {TRY{pData = new CThreadData;}CATCH_ALL(e){LeaveCriticalSection(&m_sect);THROW_LAST();}END_CATCH_ALLpData->nCount = 0;pData->pData = NULL;DEBUG_ONLY(pData->pNext = NULL);m_list.AddHead(pData);}// grow to now current sizevoid** ppvTemp;if (pData->pData == NULL)   //为CThreadData 中的pDaData指针数组分配内存,增加槽号就增加内存的分配ppvTemp = (void**)LocalAlloc(LMEM_FIXED, static_cast<UINT>(::ATL::AtlMultiplyThrow(static_cast<UINT>(m_nMax),static_cast<UINT>(sizeof(LPVOID)))));elseppvTemp = (void**)LocalReAlloc(pData->pData, static_cast<UINT>(::ATL::AtlMultiplyThrow(static_cast<UINT>(m_nMax),static_cast<UINT>(sizeof(LPVOID)))), LMEM_MOVEABLE);if (ppvTemp == NULL){LeaveCriticalSection(&m_sect);AfxThrowMemoryException();}pData->pData = ppvTemp;// initialize the newly allocated partmemset(pData->pData + pData->nCount, 0,(m_nMax - pData->nCount) * sizeof(LPVOID));pData->nCount = m_nMax;TlsSetValue(m_tlsIndex, pData);}ASSERT(pData->pData != NULL && nSlot < pData->nCount);if( pData->pData != NULL && nSlot < pData->nCount )pData->pData[nSlot] = pValue;                        //根据槽号设置CThreadData 中的pDaData(LPVOID类型,对应属于每个线程的需要保存的对象的
指针)指针数组中指针指向的对象
LeaveCriticalSection(&m_sect);}

inline void* CThreadSlotData::GetThreadValue(int nSlot){EnterCriticalSection(&m_sect);ASSERT(nSlot != 0 && nSlot < m_nMax);ASSERT(m_pSlotData != NULL);ASSERT(m_pSlotData[nSlot].dwFlags & SLOT_USED);ASSERT(m_tlsIndex != (DWORD)-1);if( nSlot <= 0 || nSlot >= m_nMax ) // check for retail builds. 槽号小于等于0 或者大于已存在的槽号  无意义{LeaveCriticalSection(&m_sect);return NULL;}CThreadData* pData = (CThreadData*)TlsGetValue(m_tlsIndex); //获取属于本线程的CThreadData对象指针if (pData == NULL || nSlot >= pData->nCount){LeaveCriticalSection(&m_sect);return NULL;}void* pRetVal = pData->pData[nSlot]; //根据槽号返回pData指针数组中对应的对象指针LeaveCriticalSection(&m_sect);return pRetVal;}



通过上面的代码可以看到CThreadLocal<_AFX_THREAD_STATE>  类型的全局对象_afxThreadState , 事实上是封装了对CThreadSlotData类型的全局指_afxThreadSlotDate

的使用,所有的工作由_afxThreadSlotDate (CThreadSlotData类对象的指针)来完成。这个类用本地线程储存实现了每个线程拥有自己的_AFX_THREAD_STATE对象。

这套机制实现起来看似很复杂,但却是相当有用的,在MFC的实现中会发现N多的调用。



如过我们自己在编写多线程程序的时候,想要为每个线程保存一个属于本线程的对象的时候可以方便的使用这个机制。比如我们想要每个线程用一个独立的DiyString对象来保存属于该线程的一些字符信息  我们可以定义一个全局CThreadLocal< DiyString>对象 g_ThreadString然后为它定义一个全局的适配函数 ,注意,利用这套机制来保存的线程对象必须继承自 CNoTrackObject,并且有默认构造函数


#include "stdafx.h"#include <iostream>#include <string>#include <afxwin.h>class DiyString:public CNoTrackObject,public std::string{public:DiyString(){}};CThreadLocal<DiyString>  g_ThreadString;DiyString * g_GetThreadString(){DiyString * ret =0;ret = g_ThreadString.GetData();return ret;}DWORD WINAPI ThreadProc(LPVOID lpParameter){DiyString * pStr = g_GetThreadString(); //DiyString * pStr = g_ThreadString;  //这样也可以 因为CThreadLocal模板类重载了 类型转换操作符pStr->append(static_cast<char *> (lpParameter));std::cout<<*pStr<<std::endl;return 0;}int _tmain(int argc, _TCHAR* argv[]){for(int i=0; i<10; ++i){char date[256] = {0};sprintf_s(date,"This is %d thread been created",i);::CreateThread(NULL,NULL,ThreadProc,date,NULL,NULL);Sleep(100);  //延迟确保date中数据被创建的线程保存到了ThreadLocal}system("pause");return 0;}


运行结果如下



这套机制在编写多线程程序的时候是很好用的,但是也有些缺点,

第一 必须包含 afxwin.h 设置使用MFC库,使用SDK的程序想要摘出来使用必须自己去修改 这个四个文件中的一些依赖

第一 没有在线程退出的时候释放资源的接口 ,用来释放已经在堆中申请的对象



0 0
原创粉丝点击