cocos2dx多线程以及线程同步 与 cocos2dx内存管理与多线程问题

cocos2d-x引擎在内部实现了一个庞大的主循环,每帧之间更新界面,如果耗时的操作放到了主线程中,游戏的界面就会卡,这是不能容忍的,游戏最基本的条件就是流畅性,这就是为什么游戏开发选择C++的原因。另外现在双核手机和四核手机越来越普遍了,是时候使用多线程来挖掘硬件的潜力了。

1.环境搭建

cocos2d-x中的多线程使用pthread就可以实现跨平台,而且也不是很难理解。使用pthread需要先配置一下工程。右击工程----->属性----->配置属性---->链接器----->输入---->附加依赖项中添加pthreadVCE2.lib,如下图

接着添加附加包含目录,右击项目,属性----->C/C++---->常规----->附加包含目录加入pthread头文件所在的目录

这样,环境就搭建起来了。

2.多线程的使用

使用pthread来实现多线程,最重要的一个函数是

 

PTW32_DLLPORT int PTW32_CDECL pthread_create (pthread_t * tid,//线程的标示   
                            const pthread_attr_t * attr,      //创建线程的参数   
                            void *(*start) (void *),          //入口函数的指针   
                            void *arg);                       //传递给线程的数据  

在HelloWorldScene.h文件中

pthread_t pidrun,pidgo;  
static void* th_run(void *r);  
static void* th_go(void *r);  

定义了两个函数和两个线程的标识。

 

然后自定义了一个类,用于给线程传递数据。Student类如下:

#pragma once   
#include <string>   
class Student  
{  
public:  
    Student(void);  
    Student(std::string name,int age,std::string sex);  
    ~Student(void);  
  
    std::string name;  
    int age;  
    std::string sex;  
  
};   

 

源文件如下

#include "Student.h"  
#include "cocos2d.h"  
  
Student::Student(void)  
{  
}  
  
  
Student::~Student(void)  
{  
    cocos2d::CCLog("delete data");  
}  
Student::Student(std::string name,int age,std::string sex)  
{  
    this->name=name;  
    this->age=age;  
    this->sex=sex;  
}  

在退出菜单的回调函数中启动两个线程:

void HelloWorld::menuCloseCallback(CCObject* pSender)  
{  
     
    Student *temp=new Student(std::string("zhycheng"),23,std::string("male"));  
    pthread_mutex_init(&mutex,NULL);  
    pthread_create(&pidrun,NULL,th_run,temp);//启动线程  
    pthread_create(&pidgo,NULL,th_go,0);  
  
}  

可以看到,将Student的指针传递给了pidrun线程,那么在pidrun线程中获得Student信息如下:

      Student *s=(Student*)(r);  
CCLog("name is %s,and age is %d,sex is %s",s->name.c_str(),s->age,s->sex.c_str());  
delete s;  

3.线程同步

使用了线程,必然就要考虑到线程同步,不同的线程同时访问资源的话,访问的顺序是不可预知的,会造成不可预知的结果。

 

这里使用pthread_mutex_t来实现同步,下面我来演示一下使用多线程实现卖票系统。卖票的时候,是由多个窗口同时卖票,这里要做到一张票不要卖出去两次,不要出现有票却无法卖的结果。

在线程函数th_run和th_go中来卖票,票的数量是一个全局变量,每卖出去一张票,就将票的数量减一。其中同步的pthread_mutex_t也是一个全局变量,就用它来实现线程同步。

void* HelloWorld::th_run(void *r)  
{  
      
    Student *s=(Student*)(r);  
    CCLog("name is %s,and age is %d,sex is %s",s->name.c_str(),s->age,s->sex.c_str());  
    delete s;  
    while(true)  
    {  
        pthread_mutex_lock(&mutex);  
        if(ticket>0)  
        {  
        CCLog("thread run sell %d",ticket);  
        ticket--;  
        pthread_mutex_unlock(&mutex);  
        }  
        else  
        {  
            pthread_mutex_unlock(&mutex);  
            break;    
        }  
      
        Sleep(1);  
        //Usleep(10);  
    }  
  
    return NULL;  
}  
void* HelloWorld::th_go(void *r)  
{  
      
    while(true)  
    {  
        pthread_mutex_lock(&mutex);  
        if(ticket>0)  
        {  
        CCLog("thread go sell %d",ticket);  
        ticket--;  
        pthread_mutex_unlock(&mutex);  
        }  
        else  
        {  
            pthread_mutex_unlock(&mutex);  
            break;  
              
        }  
          
        Sleep(1);  
          
    }  
    return NULL;  
}  


mutex被锁定后,其他线程若再想锁定mutex的话,必须等待,当该线程释放了mutex之后,其他线程才能锁定mutex。Sleep()函数可以使得该线程休眠,单位是毫秒。

4.注意

1.Sleep()函数是使得线程休眠的函数,这个函数不跨平台,仅仅在windows上能用,其他平台使用usleep。

2.在非主线程中不能使用cocos2d-x管理内存的CCObject::retain()CCObject::release() 者CCObject::autorelease(),因为CCAutoreleasePool不是线程安全的,OPENGL的上下文也不是线程安全的,所以不要再非主线程中使用cocos2d-x的API和UI操作。

 

 

cocos2dx内存管理与多线程问题:

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的嵌套对象
源码如下

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


代码下载地址:https://github.com/kaitiren/pthread-test-for-cocos2dx

 

posted on 2017-10-28 00:45  &大飞  阅读(925)  评论(0编辑  收藏  举报

导航