1、cocos2d-x线程与异步介绍cocos2d-x是一个单线程的引擎,引擎每一帧之间更新游戏的各元素的状态,以保证它们之间互不干扰,这个过程其实是一个串行的过程,单线程的好处就是无需担心对象更新引起的线程安全问题。但是当使用i/o操作时,单线程的缺点就暴漏了。
例如:游戏中的场景跳转,通常会释放当前场景资源,加载下一个场景的资源。这是一个读写操作,而这种外部存储操作十分耗时,造成主线程的阻塞,导致帧率的下降,又因为程序只有一个线程,不会中断当前执行内容去执行其他内容,所以游戏画面就很卡。
cocos2d-x为了解决这个问题,提供了一步加载功能。使用texturecahe发送一个异步加载文件的请求。texturecache内部会帮助我们建立一个新的线程来完成耗时的加载资源操作。同时,在主线程又可以执行其他操作。
除此之外,网络读写也是比较常见的耗时操作。所以,在客户端/服务器系统使用线程也是比较常见的。如httpclient中的异步功能。
2、单核与多核单核即只有一个处理器,多核既有多个处理器,现在的通信设备都是多核,如果不充分利用多核,岂不是很浪费。
单核设备中的多线程是并发的
多核设备中的多线程是并行或并发的。
什么是并行:程序中有多个线程,在单核机器上,多线程就是并行的。即主线程与其他线程交错运行的状态。例如:我们将时间片划分为100毫秒,当前100毫秒执行主线程,下一个100毫秒执行另一个线程,可能再过几个100毫秒,继续执行主线程。这样使得不会让一个线程无限期的延迟,一旦时间片到了,程序会强行中断当前线程,而去执行另一个线程。宏观上看是同时执行,其实,线程的执行还是分开执行的,这就是所谓的并发。
什么是并行:假如我们把程序运行在多核机器上,那么线程之间可以占用不同的处理器,并且独立执行,使得程序同时运行,而不需交错运行。这样的状态称为并行状态。
所以,并发是一种伪并行的状态,通过交错执行线程,来创造线程并行的假象。
3、线程安全什么是线程安全:线程安全是指代码能被多个线程调用,而不会产生灾难性的结果,如下示例:
static int count = 0; // count 是一个静态全局变量 //a方法 线程1的线程函数 void * a(void * data){ while (1) { count += 1; printf(%d\n,count); } } //b方法 线程2的线程函数 void * b(void * data){ while (1) { count += 1; printf(%d\n,count); } }
假设我们在两个线程中分别执行a和b的函数,运行程序后我们期望的结果是123456789…… 但实际上,由于线程的执行顺序是不可预知的,上述代码的预期结果与实际结果可能是不一样的。这就是线程不安全了。
如何解决线程安全问题?
首先,count变量对于两个线程,是共享数据,两个线程可以同时访问共享数据,这时就会出现线程安全问题。解决这个问题,最常见的方法就是使用线程的同步,即给数据加锁。这里的同步并不是指让线程步调一致的一起运行,而是让线程有先后次序的执行。一个执行完,下一个再执行。
线程同步使用最多的是使相同数据的内存访问互斥进行。用上面例子解释就是,线程1访问count时,不允许线程2访问,等线程1执行完,再执行线程2。一次只允许一个线程去读写数据。其他线程等待。一个形象的例子就是,a和b上厕所大便(只有一个坑),如果a在厕所里,并且将厕所门锁住,b在外等待。a解决完后解开门锁,b进入,上锁,别人同样不允许进入。这里的锁:就是我们所说的互斥量(互斥体)。通过锁定与解锁,使得在某个时间段内只有一个线程去操作共享数据。
cocos2d-x中使用了autoreleasepool进行内存管理,autoreleasepool是非线程安全的。retain、release、autorelease非线程安全。另外opengl上下文对象也是非线程安全的。但是在游戏中加载纹理图片、声音预处理和网络请求数据都需要通过多线程技术实现。
cocos2d-x引擎提供了多线程技术,cocos2d-x 3.x之前使用第三方的pthread技术,之后使用的是c++新规范中得std::thread多线程
4、pthread和thread1)pthread:互斥体类型为pthread_mutex_t表示,c++11中使用std::mutex表示。上面的代码可以写成下面的样子:
static int count = 0; // count 是一个静态全局变量 /* 保护count操作的互斥体,pthread_mutex_initializer是对互斥体变量进行初始化的特殊值 */ pthread_mutex_t count_mutex = pthread_mutex_initializer; //a方法 线程1的线程函数 void * a(void * data){ while (1) { /* 锁定保护count操作的互斥体。*/ pthread_mutex_lock (&count_mutex); count += 1; printf(%d\n,count); /* 已经完成了对count操作的处理,因此解除对互斥体的锁定。*/ pthread_mutex_nlock (&count_mutex); } }
除了互斥体外,同步工具还有信号量、条件变量。互斥量比较耗时,所以使用其他工具也可以解决更复杂的控制模式。2)std::thread多线程技术
std::thread是c++11中引入的一个新的线程库,他提供了线程管理的相关函数,还提供std::mutex(互斥量),实现线程同步。启动一个std::thread对象非常简单。见下面示例:
#include #include void callfn(){ ① std::cout 代码2是创建thread对象,参数是函数指针callfn,还可以为回调函数提供参数。代码1是回调函数的定义。代码3是讲子线程与主线程合并,使得子线程执行完成后才能继续执行主线程,同时避免了子线程还在执行,主线程已经结束而撤销。
此外,线程的创建还可以使用堆的方式分配内存,代码如下:
void callfn(){ std::cout join(); delete t1; ② t1 = nullptr; ③ return 0;}
代码1是通过堆分配内存,代码2释放线程对象,代码3防止野指针。5、声音采用线程预加载示例
#include cocos2d.h#include simpleaudioengine.husing namespace cocosdenshion;class appdelegate : private cocos2d::application{ private: std::thread *_loadingaudiothread;① void loadingaudio();② public: appdelegate(); virtual ~appdelegate(); … …};
include appdelegate.h#include helloworldscene.h using_ns_cc; appdelegate::appdelegate() { _loadingaudiothread = new std::thread(&appdelegate::loadingaudio,this); ①} appdelegate::~appdelegate() { _loadingaudiothread->join(); ② cc_safe_delete(_loadingaudiothread); ③} bool appdelegate::applicationdidfinishlaunching() { … … return true;}void appdelegate::applicationdidenterbackground() { director::getinstance()->stopanimation(); simpleaudioengine::getinstance()->pausebackgroundmusic();}void appdelegate::applicationwillenterforeground() { director::getinstance()->startanimation(); simpleaudioengine::getinstance()->resumebackgroundmusic();} void appdelegate::loadingaudio() ④{ //初始化 音乐 simpleaudioengine::getinstance()->preloadbackgroundmusic(sound/jazz.mp3); simpleaudioengine::getinstance()->preloadbackgroundmusic(sound/synth.mp3); //初始化 音效 simpleaudioengine::getinstance()->preloadeffect(sound/blip.wav);}
代码2合并线程到主线程,在析构函数中调用,join函数一般是在线程处理完成后调用。可以在析构和退出函数中调用。
6、异步加载图片cocos2d-x为我们提供了addimageasync()方法,该方法在texturecache类中,下面分析这个方法:
/* 异步添加纹理 参数为图片的资源路径 以及加载完成后进行通知的回调函数 */void texturecache::addimageasync(const std::string &path, const std::function& callback){ //创建一个纹理对象指针 texture2d *texture = nullptr; //获取资源路径 std::string fullpath = fileutils::getinstance()->fullpathforfilename(path); //如果这个纹理已经加载 则返回 auto it = _textures.find(fullpath); if( it != _textures.end() ) texture = it->second;//second为key-value中的 value if (texture != nullptr) { //纹理加载过了直接执行回调方法并终止函数 callback(texture); return; } // 第一次执行异步加载的函数时需要对保存消息结构体的队列初始化 if (_asyncstructqueue == nullptr) { //两个队列的释放会在addimageasynccallback中完成 _asyncstructqueue = new queue(); _imageinfoqueue = new deque(); // 创建一个新线程加载纹理 _loadingthread = new std::thread(&texturecache::loadimage, this); //是否退出变量 _needquit = false; } if (0 == _asyncrefcount) { /* 向scheduler注册一个更新回调函数 cocos2d-x会在这个更新函数中检查已经加载完成的纹理 然后每一帧对一个纹理进行处理 将这里纹理的信息缓存到texutrecache中 */ director::getinstance()->getscheduler()->schedule(schedule_selector(texturecache::addimageasynccallback), this, 0, false); } //异步加载纹理数据的数量 ++_asyncrefcount; //生成异步加载纹理信息的消息结构体 asyncstruct *data = new (std::nothrow) asyncstruct(fullpath, callback); //将生成的结构体加入到队列中 _asyncstructqueuemutex.lock(); _asyncstructqueue->push(data); _asyncstructqueuemutex.unlock(); //将线程解除阻塞 表示已有空位置 _sleepcondition.notify_one();}
void texturecache::addimageasynccallback(float dt){ // _imageinfoqueue双端队列用来保存在新线程中加载完成的纹理 std::deque *imagesqueue = _imageinfoqueue; _imageinfomutex.lock(); //锁定互斥提 if (imagesqueue->empty()) { _imageinfomutex.unlock(); //队列为空解锁 } else { imageinfo *imageinfo = imagesqueue->front(); //取出首部元素 image信息结构体 imagesqueue->pop_front();//删除首部元素 _imageinfomutex.unlock();//解除锁定 asyncstruct *asyncstruct = imageinfo->asyncstruct;//获取异步加载的消息结构体 image *image = imageinfo->image;//获取image指针 用于生成opengl纹理贴图 const std::string& filename = asyncstruct->filename;//获取资源文件名 //创建纹理指针 texture2d *texture = nullptr; //image指针不为空 if (image) { // 创建纹理对象 texture = new (std::nothrow) texture2d(); //由image指针生成opengl贴图 texture->initwithimage(image); #if cc_enable_cache_texture_data // cache the texture file name volatiletexturemgr::addimagetexture(texture, filename);#endif // 将纹理数据缓存 _textures.insert( std::make_pair(filename, texture) ); texture->retain(); //加入到自动释放池 texture->autorelease(); } else { auto it = _textures.find(asyncstruct->filename); if(it != _textures.end()) texture = it->second; } //取得加载完成后需要通知的函数 并进行通知 if (asyncstruct->callback) { asyncstruct->callback(texture); } //释放image if(image) { image->release(); } //释放两个结构体 delete asyncstruct; delete imageinfo; //将加载的纹理数量减一 --_asyncrefcount; /* 所有文件加载完毕 注销回调函数 */ if (0 == _asyncrefcount) { director::getinstance()->getscheduler()->unschedule(schedule_selector(texturecache::addimageasynccallback), this); } }}
7、使用实例class helloworld : public cocos2d::layer{public: // there's no 'id' in cpp, so we recommend returning the class instance pointer static cocos2d::scene* createscene(); // here's a difference. method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone virtual bool init(); virtual void onenter() override; virtual ~helloworld(); // a selector callback void menuclosecallback(cocos2d::ref* psender); void loadimages(float dt); void imageloaded(cocos2d::texture2d* texture); // implement the static create() method manually create_func(helloworld);private: int _imageoffset;};
#include helloworldscene.husing_ns_cc;scene* helloworld::createscene(){ // 'scene' is an autorelease object auto scene = scene::create(); // 'layer' is an autorelease object auto layer = helloworld::create(); // add layer as a child to scene scene->addchild(layer); // return the scene return scene;}void helloworld::onenter(){ layer::onenter(); _imageoffset = 0; auto winsize = director::getinstance()->getwinsize(); auto label = label::createwithsystemfont(loading..., , 40); label->setposition(vec2(winsize.width/2,winsize.height/2)); addchild(label,10); auto scale = scaleby::create(0.3f, 2); auto scale_back = scale->reverse(); auto seq = sequence::create(scale,scale_back, null); label->runaction(repeatforever::create(seq)); scheduleonce(cc_schedule_selector(helloworld::loadimages), 1.0f);}helloworld::~helloworld(){ director::getinstance()->gettexturecache()->unbindallimageasync(); director::getinstance()->gettexturecache()->removealltextures();}// on init you need to initialize your instancebool helloworld::init(){ ////////////////////////////// // 1. super init first if ( !layer::init() ) { return false; } size visiblesize = director::getinstance()->getvisiblesize(); vec2 origin = director::getinstance()->getvisibleorigin(); ///////////////////////////// // 2. add a menu item with x image, which is clicked to quit the program // you may modify it. // add a close icon to exit the progress. it's an autorelease object auto closeitem = menuitemimage::create( closenormal.png, closeselected.png, cc_callback_1(helloworld::menuclosecallback, this)); closeitem->setposition(vec2(origin.x + visiblesize.width - closeitem->getcontentsize().width/2 , origin.y + closeitem->getcontentsize().height/2)); // create menu, it's an autorelease object auto menu = menu::create(closeitem, null); menu->setposition(vec2::zero); this->addchild(menu, 1); ///////////////////////////// // 3. add your codes below... // add a label shows hello world // create and initialize a label auto label = label::createwithttf(hello world, fonts/marker felt.ttf, 24); // position the label on the center of the screen label->setposition(vec2(origin.x + visiblesize.width/2, origin.y + visiblesize.height - label->getcontentsize().height)); // add the label as a child to this layer this->addchild(label, 1); // add helloworld splash screen auto sprite = sprite::create(helloworld.png); // position the sprite on the center of the screen sprite->setposition(vec2(visiblesize.width/2 + origin.x, visiblesize.height/2 + origin.y)); // add the sprite as a child to this layer this->addchild(sprite, 0); return true;}void helloworld::loadimages(float dt){ for(int i = 0; i gettexturecache()->addimageasync(szspritename, cc_callback_1(helloworld::imageloaded, this)); } } director::getinstance()->gettexturecache()->addimageasync(background1.jpg, cc_callback_1(helloworld::imageloaded, this)); director::getinstance()->gettexturecache()->addimageasync(background.jpg, cc_callback_1(helloworld::imageloaded, this)); director::getinstance()->gettexturecache()->addimageasync(background.png, cc_callback_1(helloworld::imageloaded, this)); director::getinstance()->gettexturecache()->addimageasync(atlastest.png, cc_callback_1(helloworld::imageloaded, this)); director::getinstance()->gettexturecache()->addimageasync(grossini_dance_atlas.png, cc_callback_1(helloworld::imageloaded, this));}void helloworld::imageloaded(cocos2d::texture2d *texture){ auto director = director::getinstance(); auto sprite = sprite::createwithtexture(texture); sprite->setanchorpoint(vec2::anchor_bottom_left); addchild(sprite,-1); auto winsize = director->getwinsize(); int i = _imageoffset*32; sprite->setposition(vec2(i%(int)winsize.width,(i / (int)winsize.width)*32)); _imageoffset++; log(image loaded: %p,texture);}void helloworld::menuclosecallback(ref* psender){#if (cc_target_platform == cc_platform_wp8) || (cc_target_platform == cc_platform_winrt) messagebox(you pressed the close button. windows store apps do not implement a close button.,alert); return;#endif director::getinstance()->end();#if (cc_target_platform == cc_platform_ios) exit(0);#endif}
8、运行结果
参考文章:http://blog.csdn.net/u012945598/article/details/41312345
http://blog.csdn.net/tonny_guan/article/details/41017763
