MFC線程獨立對象管理機制分析

可以參考這篇博文https://blog.csdn.net/likePeak/article/details/3595982

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

聲明瞭一個全局變量CThreadLocal<_AFX_THREAD_STATE> _afxThreadState;

#define PROCESS_LOCAL(class_name, ident_name) \
	AFX_COMDAT CProcessLocal<class_name> ident_name;
PROCESS_LOCAL(_AFX_BASE_MODULE_STATE, _afxBaseModuleState)

定義了一個全局變量CProcessLocal<_AFX_BASE_MODULE_STATE> _afxBaseModuleState;

// global _afxThreadData used to allocate thread local indexes
BYTE __afxThreadData[sizeof(CThreadSlotData)];
CThreadSlotData* _afxThreadData;
// AFX_MODULE_THREAD_STATE (local to thread *and* module)
class AFX_MODULE_THREAD_STATE : public CNoTrackObject
{
}
// AFX_MODULE_STATE (global data for a module)
class AFX_MODULE_STATE : public CNoTrackObject
{
}
class _AFX_THREAD_STATE : public CNoTrackObject
{
}	
#ifdef _AFXDLL
#define _AFX_CMDTARGET_GETSTATE() (m_pModuleState)
#else
#define _AFX_CMDTARGET_GETSTATE() (AfxGetModuleState())
#endif
struct CThreadData : public CNoTrackObject
{
	CThreadData* pNext; // required to be member of CSimpleList
	int nCount;         // current size of pData
	LPVOID* pData;      // actual thread local data (indexed by nSlot)
};

_afxThreadData這個全局變量用來實現存取獲取所有mfc線程的線程局部數據的功能。

class CThreadSlotData
{
public:
	CThreadSlotData();

// Operations
	int AllocSlot();
	void FreeSlot(int nSlot);	
	void SetValue(int nSlot, void* pValue);
	// delete all values in process/thread
	void DeleteValues(HINSTANCE hInst, BOOL bAll = FALSE);
	// assign instance handle to just constructed slots
	void AssignInstance(HINSTANCE hInst);

// Implementation
	DWORD 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 slots
	int 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 structures
	CRITICAL_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();
};

TlsAlloc()分配一個m_tlsIndex用來給所有線程分配TLS數據。_afxThreadData是MFC全局唯一的,那麼m_tlsIndex也是全局唯一的。m_list這裏面每一個CThreadData就代表一個MFC線程的數據。

假若我們加入THREAD_LOCAL(TStruct, threadState3)產生一個全局變量:CThreadLocal<TStruct> threadState3;這個threadState3是全局變量,那麼它的構造函數也應該是在主線程裏調用的,但是產生TStruct的構造函數是在某個線程裏第一次調用threadState3->a時調用的。

struct TStruct:public CNoTrackObject
{
	int a;
	TStruct()
	{
		a = 111;;
	}
};
DWORD __stdcall pfnThread2(LPVOID)
{	
	int iA3 = threadState3->a;
	return 0;
}

threadState3->a會調用下面CThreadLocal的這個函數。這裏的TYPE其實就是TStruct

AFX_INLINE TYPE* operator->()
{ 
	return GetData(); 
}

繼續往下走,到CThreadLocal的下面這個函數

AFX_INLINE TYPE* GetData()
	{
		TYPE* pData = (TYPE*)CThreadLocalObject::GetData(&CreateObject);
		ENSURE(pData != NULL);
		return pData;
	}
static CNoTrackObject* AFXAPI CreateObject()
	{ return new TYPE; }

繼續走到CThreadLocalObject::GetData

CNoTrackObject* CThreadLocalObject::GetData(
	CNoTrackObject* (AFXAPI* pfnCreateObject)())
{
	if (m_nSlot == 0)
	{
		if (_afxThreadData == NULL)//全局的這玩意兒第一調用時new一下下
		{
			_afxThreadData = new(__afxThreadData) CThreadSlotData;
		}
		m_nSlot = _afxThreadData->AllocSlot();//找一個還未使用的slot號
	}
//下面這個GetThreadValue就是根據m_nSlot找到相應的PVOID
	CNoTrackObject* pValue = static_cast<CNoTrackObject*>(_afxThreadData->GetThreadValue(m_nSlot));
	if (pValue == NULL)//如果是空就存放一下
	{
		// allocate zero-init object
		pValue = (*pfnCreateObject)();//這個就是 new TYPE;
		// set tls data to newly created object
		_afxThreadData->SetValue(m_nSlot, pValue);//把這個pValue 即那個PVOID存到相應槽
	}
	return pValue;
}

m_nSlot這個玩意一個線程或者說一個CThreadLocal有一個。它用在哪呢,用在全局唯一的那個_afxThreadDatam_pSlotDatam_list以及一個PVOID數組,這個數組內存是由LocalAlloc分配的,這個數組全局的,供所有線程使用。

// special version of CThreadSlotData::GetData that only works with
// thread local storage (and not process local storage)
// this version is inlined and simplified for speed
inline void* CThreadSlotData::GetThreadValue(int nSlot)
{
	EnterCriticalSection(&m_sect);
	if( nSlot <= 0 || nSlot >= m_nMax ) // check for retail builds.
	{
		LeaveCriticalSection(&m_sect);
		return NULL;
	}
//第一次調用的時候,返回NULL
	CThreadData* pData = (CThreadData*)TlsGetValue(m_tlsIndex);
	if (pData == NULL || nSlot >= pData->nCount)
	{
		LeaveCriticalSection(&m_sect);
		return NULL;
	}
//CThreadData* pData不爲空,就能返回pData->pData[nSlot],因爲TlsSetValue的時候,設	//置這個void*,即我們的TYPE*
	void* pRetVal = pData->pData[nSlot];
	//LeaveCriticalSection(&m_sect);
	return pRetVal;
}

如果上面這個函數返回NULL,說明此線程是第一次調用GetData,就要調用下面這個函數了。

void CThreadSlotData::SetValue(int nSlot, void* pValue)
{
	EnterCriticalSection(&m_sect);
	if( nSlot <= 0 || nSlot >= m_nMax ) // check for retail builds.
	{
		LeaveCriticalSection(&m_sect);
		return;
	}
	CThreadData* pData = (CThreadData*)TlsGetValue(m_tlsIndex);
	if (pData == NULL || nSlot >= pData->nCount && pValue != NULL)
	{
		// if pData is NULL then this thread has not been visited yet
		if (pData == NULL)
		{
			TRY
			{
				pData = new CThreadData;
			}
			CATCH_ALL(e)
			{
				LeaveCriticalSection(&m_sect);
				THROW_LAST();
			}
			END_CATCH_ALL
			pData->nCount = 0;
			pData->pData = NULL;
			DEBUG_ONLY(pData->pNext = NULL);

			m_list.AddHead(pData);//將我們線程的CThreadData放入全局list
		}

		// grow to now current size
//每一個線程的CThreadData的pData 全部指向ppvTemp,即這個全局局部堆。然後每一	//個CThreadData再根據nSlot從這個堆找到自己線程的那個真正數據TYPE*指向那玩意,	//即我們創建的TStruct對象
		void** ppvTemp;
		if (pData->pData == NULL)
			ppvTemp = (void**)LocalAlloc(LMEM_FIXED, static_cast<UINT>(::ATL::AtlMultiplyThrow(static_cast<UINT>(m_nMax),static_cast<UINT>(sizeof(LPVOID)))));
		else
			ppvTemp = (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();
		}
//全指向這個LPVOID數組的第一個元素
		pData->pData = ppvTemp;
		// initialize the newly allocated part
		memset(pData->pData + pData->nCount, 0,
			(m_nMax - pData->nCount) * sizeof(LPVOID));
		pData->nCount = m_nMax;
//TlsSetValue傳遞的是CThreadData*參數,CThreadData裏面放着我們自己的線程數據
		TlsSetValue(m_tlsIndex, pData);
	}
	if( pData->pData != NULL && nSlot < pData->nCount )
		pData->pData[nSlot] = pValue;//雖然pData->pData指向局部堆第一個LPVOID,但是利								//用nSlot在ppvTemp 相應位置存放我們自己的線程數據
	LeaveCriticalSection(&m_sect);
}
HLOCAL LocalAlloc(
    __in UINT uFlags,
    __in SIZE_T uBytes
    );

上面用到的LocalAlloc看着很咋呼,其實API很簡單不是。就是分配m_nMax個LPVOID。

下面看一下分配slot的代碼:

初始化狀態爲:

	m_nAlloc = 0;
	m_nRover = 1;   // first slot (0) is always reserved
	m_nMax = 0;
int CThreadSlotData::AllocSlot()
{
	EnterCriticalSection(&m_sect);
	int nAlloc = m_nAlloc;
	int nSlot = m_nRover;
	if (nSlot >= nAlloc || (m_pSlotData[nSlot].dwFlags & SLOT_USED))
	{
		// search for first free slot, starting at beginning
		for (nSlot = 1;
			nSlot < nAlloc && (m_pSlotData[nSlot].dwFlags & SLOT_USED); nSlot++)
			;

		// if none found, need to allocate more space
		if (nSlot >= nAlloc)
		{
			// realloc memory for the bit array and the slot memory
			int nNewAlloc = m_nAlloc+32;//每次增加32,m_nAlloc相當於vector裏的容量
			HGLOBAL hSlotData;
			if (m_pSlotData == NULL)
			{
				hSlotData = GlobalAlloc(GMEM_MOVEABLE, static_cast<UINT>(::ATL::AtlMultiplyThrow(static_cast<UINT>(nNewAlloc),static_cast<UINT>(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);
			}
//pSlotData的數量與m_nAlloc是相等的,下面這句代碼就是把分配內存賦值給pSlotData
			CSlotData* pSlotData = (CSlotData*)GlobalLock(hSlotData);

			// always zero initialize after success
			memset(pSlotData+m_nAlloc, 0, (nNewAlloc-m_nAlloc)*sizeof(CSlotData));
			m_nAlloc = nNewAlloc;
			m_pSlotData = pSlotData;
		}
	}
	// adjust m_nMax to largest slot ever allocated
//m_nMax比曾經分配的最大的nSlot要大1,可能是因爲第0號slot是不用的,所以加1
	if (nSlot >= m_nMax)
		m_nMax = nSlot+1;

	ASSERT(!(m_pSlotData[nSlot].dwFlags & SLOT_USED));
	m_pSlotData[nSlot].dwFlags |= SLOT_USED;
	// 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
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章