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

最近使用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,来管理该线程的autorelease poo的嵌套。源码如下

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


Home.png (26.9 KB)

Yeap, cocos2d-x is not thread-safe.
I think your implementation has following issues:

  1. CCPoolManager::getCurReleasePoolStack should add lock, or it may get wrong pointer of autorelease pool.

  2. retain(), release(), autorelease are not thread-safe, so there may be an issue that thread switching happened when using these functions.
    And even more serious that an object is shared between threads. Of course we can add lock in these functions, but it is a big overhead, because
    autorelease pool is cleared every frame. And any waiting caused by it is not acceptable for rendering thread.

So i suggest you not invoking any engine function in new thread, just use new thread in net connecting, receiving or sending datas through network.

PS: Please use English in this forum.
Thank you.

嗯,谢谢探讨

1.是否需要在 CCPoolManager::getCurReleasePoolStack 上面加锁的问题?
我当初的确在上面加过锁,但是后来发现存在非递归锁的问题导致死锁。所以就放弃了,改在push,pop,addobject,removeobject上面加,这样可以确保在他们上面执行的操作在多线程下面是安全的。

2.是存在这个问题,所以在使用上面的确需要比较小心处理。多线程间的共享变量尽量不要使用。我的解法主要是在多线程情况下希望解决autorelease的嵌套导致不安全的情况。

最后,其实我赞同你关于多线程下采用通信解决对象共享的办法。而且也不太建议多线程下使用autorelease的