cocos2d-x引擎在内部实现了一个庞大的主循环,每帧之间更新界面,如果耗时的操作放到了主线程中,游戏的界面就会卡,这是不能容忍的,游戏最基本的条件就是流畅性,这就是为什么游戏开发选择C++的原因。另外现在双核手机和四核手机越来越普遍了,是时候使用多线程来挖掘硬件的潜力了。
1.环境搭建
cocos2d-x中的多线程使用pthread就可以实现跨平台,而且也不是很难理解。使用pthread需要先配置一下工程。右击工程----->属性----->配置属性---->链接器----->输入---->附加依赖项中添加pthreadVCE2.lib,如下图
接着添加附加包含目录,右击项目,属性----->C/C++---->常规----->附加包含目录加入pthread头文件所在的目录
这样,环境就搭建起来了。
2.多线程的使用
使用pthread来实现多线程,最重要的一个函数是
[cpp] view plain copy
- int PTW32_CDECL pthread_create (pthread_t * tid,//线程的标示
- const pthread_attr_t * attr, //创建线程的参数
- void *(*start) (void *), //入口函数的指针
- void *arg); //传递给线程的数据
[cpp] view plain copy
- PTW32_DLLPORT int PTW32_CDECL pthread_create (pthread_t * tid,//线程的标示
- const pthread_attr_t * attr, //创建线程的参数
- void *(*start) (void *), //入口函数的指针
- void *arg); //传递给线程的数据
在HelloWorldScene.h文件中
[cpp] view plain copy
- pthread_t pidrun,pidgo;
- static void* th_run(void
- static void* th_go(void
[cpp] view plain copy
- pthread_t pidrun,pidgo;
- static void* th_run(void *r);
- static void* th_go(void *r);
定义了两个函数和两个线程的标识。
然后自定义了一个类,用于给线程传递数据。Student类如下:
[cpp] view plain copy
- #pragma once
- #include <string>
- class
- {
- public:
- void);
- int
- void);
- std::string name;
- int
- std::string sex;
- };
[cpp] view plain copy
- #pragma once
- #include <string>
- class Student
- {
- public:
- void);
- int age,std::string sex);
- void);
- std::string name;
- int age;
- std::string sex;
- };
源文件如下
[cpp] view plain copy
- #include "Student.h"
- #include "cocos2d.h"
- void)
- {
- }
- void)
- {
- "delete data");
- }
- int
- {
- this->name=name;
- this->age=age;
- this->sex=sex;
- }
[cpp] view plain copy
- #include "Student.h"
- #include "cocos2d.h"
- Student::Student(void)
- {
- }
- Student::~Student(void)
- {
- "delete data");
- }
- Student::Student(std::string name,int age,std::string sex)
- {
- this->name=name;
- this->age=age;
- this->sex=sex;
- }
在退出菜单的回调函数中启动两个线程:
[cpp] view plain copy
- void
- {
- new Student(std::string("zhycheng"),23,std::string("male"));
- pthread_mutex_init(&mutex,NULL);
- //启动线程
- pthread_create(&pidgo,NULL,th_go,0);
- }
[cpp] view plain copy
- void HelloWorld::menuCloseCallback(CCObject* pSender)
- {
- new Student(std::string("zhycheng"),23,std::string("male"));
- pthread_mutex_init(&mutex,NULL);
- //启动线程
- pthread_create(&pidgo,NULL,th_go,0);
- }
可以看到,将Student的指针传递给了pidrun线程,那么在pidrun线程中获得Student信息如下:
[cpp] view plain copy
- Student *s=(Student*)(r);
- "name is %s,and age is %d,sex is %s",s->name.c_str(),s->age,s->sex.c_str());
- delete
[cpp] view plain copy
- 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也是一个全局变量,就用它来实现线程同步。
[cpp] view plain copy
- void* HelloWorld::th_run(void
- {
- Student *s=(Student*)(r);
- "name is %s,and age is %d,sex is %s",s->name.c_str(),s->age,s->sex.c_str());
- delete
- while(true)
- {
- pthread_mutex_lock(&mutex);
- if(ticket>0)
- {
- "thread run sell %d",ticket);
- ticket--;
- pthread_mutex_unlock(&mutex);
- }
- else
- {
- pthread_mutex_unlock(&mutex);
- break;
- }
- Sleep(1);
- //Usleep(10);
- }
- return
- }
[cpp] view plain copy
- void* HelloWorld::th_run(void *r)
- {
- Student *s=(Student*)(r);
- "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)
- {
- "thread run sell %d",ticket);
- ticket--;
- pthread_mutex_unlock(&mutex);
- }
- else
- {
- pthread_mutex_unlock(&mutex);
- break;
- }
- Sleep(1);
- //Usleep(10);
- }
- return NULL;
- }
[cpp] view plain copy
- void* HelloWorld::th_go(void
- {
- while(true)
- {
- pthread_mutex_lock(&mutex);
- if(ticket>0)
- {
- "thread go sell %d",ticket);
- ticket--;
- pthread_mutex_unlock(&mutex);
- }
- else
- {
- pthread_mutex_unlock(&mutex);
- break;
- }
- Sleep(1);
- }
- return
- }
[cpp] view plain copy
- void* HelloWorld::th_go(void *r)
- {
- while(true)
- {
- pthread_mutex_lock(&mutex);
- if(ticket>0)
- {
- "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()函数可以使得该线程休眠,单位是毫秒。下面是卖票的结果:
[cpp] view plain copy
- name is zhycheng,and age is 23,sex is male
- delete
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
[cpp] view plain copy
- name is zhycheng,and age is 23,sex is male
- delete data
- thread run sell 100
- thread run sell 99
- thread go sell 98
- thread go sell 97
- thread run sell 96
- thread go sell 95
- thread go sell 94
- thread run sell 93
- thread go sell 92
- thread run sell 91
- thread go sell 90
- thread go sell 89
- thread run sell 88
- thread go sell 87
- thread run sell 86
- thread go sell 85
- thread run sell 84
- thread go sell 83
- thread run sell 82
- thread go sell 81
- thread run sell 80
- thread go sell 79
- thread run sell 78
- thread go sell 77
- thread run sell 76
- thread go sell 75
- thread run sell 74
- thread go sell 73
- thread run sell 72
- thread go sell 71
- thread run sell 70
- thread go sell 69
- thread go sell 68
- thread run sell 67
- thread go sell 66
- thread run sell 65
- thread go sell 64
- thread run sell 63
- thread go sell 62
- thread run sell 61
- thread go sell 60
- thread run sell 59
- thread go sell 58
- thread run sell 57
- thread go sell 56
- thread run sell 55
- thread go sell 54
- thread run sell 53
- thread run sell 52
- thread go sell 51
- thread run sell 50
- thread go sell 49
- thread run sell 48
- thread go sell 47
- thread run sell 46
- thread go sell 45
- thread run sell 44
- thread run sell 43
- thread go sell 42
- thread run sell 41
- thread run sell 40
- thread go sell 39
- thread run sell 38
- thread run sell 37
- thread run sell 36
- thread run sell 35
- thread go sell 34
- thread run sell 33
- thread run sell 32
- thread go sell 31
- thread run sell 30
- thread run sell 29
- thread run sell 28
- thread run sell 27
- thread run sell 26
- thread run sell 25
- thread go sell 24
- thread run sell 23
- thread go sell 22
- thread go sell 21
- thread run sell 20
- thread go sell 19
- thread run sell 18
- thread run sell 17
- thread go sell 16
- thread run sell 15
- thread go sell 14
- thread go sell 13
- thread run sell 12
- thread go sell 11
- thread go sell 10
- thread run sell 9
- thread go sell 8
- thread run sell 7
- thread go sell 6
- thread go sell 5
- thread run sell 4
- thread go sell 3
- thread run sell 2
- thread run sell 1
可以看到,这个打印结果正确无误。如果不加mutex会是什么样的结果呢,我将线程同步的mutex注释掉,输出的结果为:
[cpp] view plain copy
- name is zhycheng,and age is 23,sex is male
- delete
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
- thread
[cpp] view plain copy
- name is zhycheng,and age is 23,sex is male
- delete data
- thread run sell 100
- thread run sell 99
- thread run sell 98
- thread go sell 97
- thread run sell 96
- thread go sell 95
- thread run sell 94
- thread go sell 94
- thread run sell 92
- thread run sell 91
- thread go sell 90
- thread run sell 89
- thread go sell 88
- thread run sell 87
- thread run sell 86
- thread go sell 86
- thread go sell 84
- thread run sell 83
- thread go sell 82
- thread run sell 81
- thread go sell 80
- thread run sell 79
- thread run sell 78
- thread go sell 77
- thread run sell 76
- thread run sell 75
- thread go sell 74
- thread run sell 73
- thread go sell 72
- thread run sell 71
- thread go sell 70
- thread go sell 69
- thread run sell 68
- thread go sell 67
- thread go sell 66
- thread run sell 65
- thread go sell 64
- thread go sell 63
- thread run sell 62
- thread go sell 61
- thread run sell 60
- thread run sell 59
- thread run sell 58
- thread run sell 57
- thread run sell 56
- thread run sell 55
- thread go sell 54
- thread run sell 54
- thread go sell 52
- thread run sell 52
- thread go sell 50
- thread run sell 50
- thread go sell 49
- thread run sell 47
- thread go sell 47
- thread go sell 45
- thread run sell 45
- thread run sell 43thread go sell 43
- thread run sell 41
- thread go sell 41
- thread go sell 39
- thread run sell 39
- thread run sell 37
- thread go sell 37
- thread go sell 35
- thread run sell 35
- thread go sell 33thread run sell 33
- thread go sell 31thread run sell 31
- thread go sell 29
- thread run sell 29
- thread go sell 27
- thread run sell 27
- thread go sell 25
- thread run sell 25
- thread run sell 23
- thread go sell 23
- thread run sell 21
- thread go sell 21
- thread go sell 19
- thread run sell 19
- thread run sell 17
- thread go sell 17
- thread go sell 15
- thread run sell 15
- thread run sell 13
- thread go sell 13
- thread run sell 11thread go sell 11
- thread go sell 9
- thread run sell 9
- thread run sell 7
- thread go sell 7
- thread go sell 5thread run sell 5
- thread go sell 3
- thread run sell 3
- thread go sell 1
- thread run sell 1
可以看到,有的票卖了两次,有的票就没卖。
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 |