感谢alienblue的分享!
最近使用Cocos2d-x开发游戏,发现Cocos2d-x的内存管理采用Objective-C的机制,大喜过望。因为只要坚持Objective-C的原则“谁创建谁释放,谁备份谁释放”的原则即可确保内存使用不易出现Bug。
但是因为本身开放的游戏需要使用到多线程技术,导致测试的时候总是莫名其妙的导致空指针错误。而且是随机出现,纠结了2天无果后,开始怀疑Cocos2d-X的内存本身管理可能存在问题。怀着这样的想法,一步一步的调试,发现经常出现指针异常的变量总是在调用autorelease后一会就莫名其妙再使用的时候就抛异常。狠下心,在它的析构函数里面断点+Log输出信息。发现对象被释放了。一时也很迷糊,因为对象只是autorelease,并没有真正释放,是谁导致它释放的?
然后就去看了CCAutoreleasePool的源码,发现存在Cocos2d-X的内存管理在多线程的情况下存在如下问题

如图:thread 1和thread 2是独立的两个线程,它们之间存在CPU分配的交叉集,我们在time 1的时候push一个autorelease的自动释放池,在该线程的末尾,即time 3的时候pop它。同理在thread 2的线程里面,在time 2的时候push一个自动释放池,在time 4的时候释放它,即Pop.
此时我们假设在thread 2分配得到CPU的时候有一个对象obj自动释放,即obj-autorelease().那么在time 3的时候会发生是么事情呢?
答案很简单,就是obj在time 3的时候就被释放了,而我们期望它在time 4的时候才释放。所以就导致我上面说的,在多线程下面,cocos2d-x的autorelease变量会发生莫名其妙的指针异常。
解决办法:在PoolManager给每个线程根据pthread_t的线程id生成一个CCArray的stack的嵌套管理自动释放池。源码如下
所以我在Push的时候根据当前线程的pthread_t的线程id生成一个CCArray的stack来存储该线程对应的Autoreleasepool的嵌套对象
源码如下
class CC_DLL CCPoolManager
{
/////【diff - begin】- by layne//////
//CCArray* m_pReleasePoolStack;
//CCAutoreleasePool* m_pCurReleasePool;
// CCAutoreleasePool* getCurReleasePool();
CCDictionary* m_pReleasePoolMultiStack;
pthread_mutex_t m_mutex; // for multi-thread lock
CCArray* getCurReleasePoolStack();
CCAutoreleasePool* getCurReleasePool(bool autoCreate = false);
/////【diff - end】- by layne//////
public:
CCPoolManager();
~CCPoolManager();
void finalize();
void push();
void pop();
void removeObject(CCObject* pObject);
void addObject(CCObject* pObject);
static CCPoolManager* sharedPoolManager();
static void purgePoolManager();
friend class CCAutoreleasePool;
};


//--------------------------------------------------------------------
//
// CCPoolManager
//
//--------------------------------------------------------------------
/////【diff - begin】- by layne//////
CCPoolManager* CCPoolManager::sharedPoolManager()
{
if (s_pPoolManager == NULL)
{
s_pPoolManager = new CCPoolManager();
}
return s_pPoolManager;
}
void CCPoolManager::purgePoolManager()
{
CC_SAFE_DELETE(s_pPoolManager);
}
CCPoolManager::CCPoolManager()
{
// m_pReleasePoolStack = new CCArray();
// m_pReleasePoolStack->init();
// m_pCurReleasePool = 0;
m_pReleasePoolMultiStack = new CCDictionary();
}
CCPoolManager::~CCPoolManager()
{
// finalize();
// // we only release the last autorelease pool here
// m_pCurReleasePool = 0;
// m_pReleasePoolStack->removeObjectAtIndex(0);
//
// CC_SAFE_DELETE(m_pReleasePoolStack);
finalize();
CC_SAFE_DELETE(m_pReleasePoolMultiStack);
}
void CCPoolManager::finalize()
{
if(m_pReleasePoolMultiStack->count() > 0)
{
//CCAutoreleasePool* pReleasePool;
CCObject* pkey = NULL;
CCARRAY_FOREACH(m_pReleasePoolMultiStack->allKeys(), pkey)
{
if(!pkey)
break;
CCInteger *key = (CCInteger*)pkey;
CCArray *poolStack = (CCArray *)m_pReleasePoolMultiStack->objectForKey(key->getValue());
CCObject* pObj = NULL;
CCARRAY_FOREACH(poolStack, pObj)
{
if(!pObj)
break;
CCAutoreleasePool* pPool = (CCAutoreleasePool*)pObj;
pPool->clear();
}
}
}
}
void CCPoolManager::push()
{
// CCAutoreleasePool* pPool = new CCAutoreleasePool(); //ref = 1
// m_pCurReleasePool = pPool;
//
// m_pReleasePoolStack->addObject(pPool); //ref = 2
//
// pPool->release(); //ref = 1
pthread_mutex_lock(&m_mutex);
CCArray* pCurReleasePoolStack = getCurReleasePoolStack();
CCAutoreleasePool* pPool = new CCAutoreleasePool(); //ref = 1
pCurReleasePoolStack->addObject(pPool); //ref = 2
pPool->release(); //ref = 1
pthread_mutex_unlock(&m_mutex);
}
void CCPoolManager::pop()
{
// if (! m_pCurReleasePool)
// {
// return;
// }
//
// int nCount = m_pReleasePoolStack->count();
//
// m_pCurReleasePool->clear();
//
// if(nCount > 1)
// {
// m_pReleasePoolStack->removeObjectAtIndex(nCount-1);
//
// // if(nCount > 1)
// // {
// // m_pCurReleasePool = m_pReleasePoolStack->objectAtIndex(nCount - 2);
// // return;
// // }
// m_pCurReleasePool = (CCAutoreleasePool*)m_pReleasePoolStack->objectAtIndex(nCount - 2);
// }
//
// /*m_pCurReleasePool = NULL;*/
pthread_mutex_lock(&m_mutex);
CCArray* pCurReleasePoolStack = getCurReleasePoolStack();
CCAutoreleasePool* pCurReleasePool = getCurReleasePool();
if (pCurReleasePoolStack && pCurReleasePool)
{
int nCount = pCurReleasePoolStack->count();
pCurReleasePool->clear();
if(nCount > 1)
{
pCurReleasePoolStack->removeObject(pCurReleasePool);
}
}
pthread_mutex_unlock(&m_mutex);
}
void CCPoolManager::removeObject(CCObject* pObject)
{
// CCAssert(m_pCurReleasePool, "current auto release pool should not be null");
//
// m_pCurReleasePool->removeObject(pObject);
pthread_mutex_lock(&m_mutex);
CCAutoreleasePool* pCurReleasePool = getCurReleasePool();
CCAssert(pCurReleasePool, "current auto release pool should not be null");
pCurReleasePool->removeObject(pObject);
pthread_mutex_unlock(&m_mutex);
}
void CCPoolManager::addObject(CCObject* pObject)
{
// getCurReleasePool()->addObject(pObject);
pthread_mutex_lock(&m_mutex);
CCAutoreleasePool* pCurReleasePool = getCurReleasePool(true);
CCAssert(pCurReleasePool, "current auto release pool should not be null");
pCurReleasePool->addObject(pObject);
pthread_mutex_unlock(&m_mutex);
}
CCArray* CCPoolManager::getCurReleasePoolStack()
{
CCArray* pPoolStack = NULL;
pthread_t tid = pthread_self();
if(m_pReleasePoolMultiStack->count() > 0)
{
pPoolStack = (CCArray*)m_pReleasePoolMultiStack->objectForKey((int)tid);
}
if (!pPoolStack) {
pPoolStack = new CCArray();
m_pReleasePoolMultiStack->setObject(pPoolStack, (int)tid);
pPoolStack->release();
}
return pPoolStack;
}
CCAutoreleasePool* CCPoolManager::getCurReleasePool(bool autoCreate)
{
// if(!m_pCurReleasePool)
// {
// push();
// }
//
// CCAssert(m_pCurReleasePool, "current auto release pool should not be null");
//
// return m_pCurReleasePool;
CCAutoreleasePool* pReleasePool = NULL;
CCArray* pPoolStack = getCurReleasePoolStack();
if(pPoolStack->count() > 0)
{
pReleasePool = (CCAutoreleasePool*)pPoolStack->lastObject();
}
if (!pReleasePool && autoCreate) {
CCAutoreleasePool* pPool = new CCAutoreleasePool(); //ref = 1
pPoolStack->addObject(pPool); //ref = 2
pPool->release(); //ref = 1
pReleasePool = pPool;
}
return pReleasePool;
}
/////【diff - end】- by layne//////