Cocos2d-x内存管理支持多线程

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


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;
};

实现文件

/////【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//////