如摘要所述,经过断点后发现如下代码有问题:
CNoTrackObject* CThreadLocalObject::GetData(CNoTrackObject* (*pfnCreateObject)())
{
if (m_nSlot == 0) {
if (_afxThreadData == nullptr) {
_afxThreadData = new (__afxThreadData) CThreadSlotData;
//OutputDebugString(_T("New CThreadSlotData\n"));
}
m_nSlot = _afxThreadData->AllocSlot();
}
// ...
}
这里面_afxThreadData的new执行了多次,很明显是由于多个线程在上一行if判断_afxThreadData为nullptr后即被挂起,之后被调度执行时,出现这种情形:一个线程的AllocSlot已经调用过了,另一个线程跑过来将_afxThreadData又new了一遍,抹掉了之前AllocSlot里面做的各种关键操作,状态彻底混乱了。 于是,我尝试在afxtls.cpp里写了下面一个类来做这里的new操作:
class CTlsInitializer
{
public:
CTlsInitializer() {
if (_afxThreadData == nullptr) {
_afxThreadData = new (__afxThreadData) CThreadSlotData;
}
}
};
CTlsInitializer gInitializer;
实践证明结果正确了,之后无论怎么运行程序都是正确的结果,release和debug都不崩溃。 后来想了想,其实只要这段new能无竞争的跑一遍,以后就不会有问题了,像上面单独弄一个CTlsInitializer gInitializer;
虽然也可以,但其实只要在主线程里调用一下CThreadLocal的GetData函数,即可达到同样的目的。
CThreadLocal<MyThreadData1> g_myThreadData1;
CThreadLocal<MyThreadData2> g_myThreadData2;
// ...
int main() {
//g_myThreadData1.operator->();
(MyThreadData1*)g_myThreadData1; // 与上一行等价. 不用所有的CThreadLocal对象都做这个操作,只要任意一个即可。
return 0;
}
运行效果和上面一样,也是正确的。不过从实用的角度,还是CTlsInitializer gInitializer;
为好。