cocos 线程相关

来源:互联网 发布:融学软件下载 编辑:程序博客网 时间:2024/06/07 16:15

【转自】http://www.cnblogs.com/yyxt/p/4087123.html

(1)单线程的尴尬

加载几十张图片到内存中,庞大计算消耗了太多时间,阻塞了主循环的正常运行

(2)pthead

pthread 是一套 POSIX 标准线程库,可以运行在各个平台上,包括 Android、iOS 和 Windows,也是 Cocos2d-x 官方推荐的多线程库。它使用 C 语言开发,

提供非常友好也足够简洁的开发接口。一个线程的创建通常是这样的:

void* justAnotherTest(void *arg){    LOG_FUNCTION_LIFE;    //在这里写入新线程将要执行的代码    return NULL;}void testThread(){    LOG_FUNCTION_LIFE;    pthread_t tid;    pthread_create(&tid, NULL, &justAnotherTest, NULL);}
这里我们在testThread函数中用pthread_create创建了一个线程,新线程的入口为justAnotherTest函数。pthread_create函数的代码如下所示:

PTW32_DLLPORT int PTW32_CDECL pthread_create (pthread_t * tid,//线程的标示                               const pthread_attr_t * attr,      //创建线程的参数                               void *(*start) (void *),          //入口函数的指针                               void *arg);                       //传递给线程的数据
pthread_create 是创建新线程的方法,它的第一个参数指定一个标识的地址,用于返回创建的线程标识;第二个参数是创建线程的参数,在不需要设置任何参数的情况下,只需传入 NULL 即可;第三个参数则是线程入口函数的指针,被指定为 void*(void*)的形式。函数指针接受的唯一参数来源于调用 pthread_create 函数时所传入的第四个参数,可以用于传递用户数据。

(3)线程安全

使用线程就不得不提线程安全问题。线程安全问题来源于不同线程的执行顺序是不可预测的,线程调度都视系统当时的状态而定,尤其是直接或间接的全局共享变

量。如果不同线程间都存在着读写访问,就很可能出现运行结果不可控的问题。

在 Cocos2d-x 中,最大的线程安全隐患是内存管理。引擎明确声明了 retain、release 和 autorelease 三个方法都不是线程安全的如果在不同的线程间对同

一个对象作内存管理,可能会出现严重的内存泄露或野指针问题。比如说,如果我们按照下述代码加载图片资源,就很可能出现找不到图片的报错——可能出现这

样的情况,当主线程执行到CCSprite::Create创建精灵的时候,上面的线程还没有执行或者没有执行完成图片资源的加载,这时就可能出现找不到图片。

void* loadResources(void *arg){    LOG_FUNCTION_LIFE;    CCTextureCache::sharedTextureCache()->addImage("fish.png");    return NULL;}void makeAFish(){    LOG_FUNCTION_LIFE;    pthread_t tid;    pthread_create(&tid, NULL, &loadResources, NULL);    CCSprite* sp = CCSprite::create("fish.png");}
在新的线程中对缓存的调用所产生的一系列内存管理操作更可能导致系统崩溃。

因此,使用多线程的首要原则是,在新建立的线程中不要使用任何 Cocos2d-x 内建的内存管理,也不要调用任何引擎提供的函数或方法,因为那可能会导致

Cocos2d-x 内存管理错误

同样,OpenGL 的各个接口函数也不是线程安全的。也就是说,一切和绘图直接相关的操作都应该放在主线程内执行,而不是在新建线程内执行
有关内存详细可参考【转自】http://blog.csdn.net/kaitiren/article/details/14453313

(4)线程间任务安排

使用并发编程的最直接目的是保证界面流畅,这也是引擎占据主线程的原因。因此,除了界面相关的代码外,其他操作都可以放入新的线程中执行,主要包括文件

读写和网络通信两类。

文件读写涉及外部存储操作,这和内存、CPU 都不在一个响应级别上。如果将其放入主线程中,就可能会造成阻塞,尤为严重的是大型图片的载入。对于碎图压

缩后的大型纹理和高分辨率的背景图,一次加载可能耗费 0.2 s 以上的时间,如果完全放在主线程内,会阻塞主线程相当长的时间,导致画面停滞,游戏体验很

糟糕。在一些大型的卷轴类游戏中,这类问题尤为明显。考虑到这个问题,Cocos2d-x 为我们提供了一个异步加载图片的接口,不会阻塞主线程,其内部正是采

用了新建线程的办法。

尽管引擎已经为我们提供了异步加载图片缓存的方式,但考虑到对图片资源的加密解密过程是十分耗费计算资源的,我们还是有必要单开一个线程执行这一系列操

作。另一个值得使用并发编程的是网络通信。网络通信可能比文件读写要慢一个数量级。一般的网络通信库都会提供异步传输形式,我们只需要注意选择就好。

(5)线程同步

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

实现源码可以知道它是使用pthread_mutex_t来实现同步:


cococs2d-x 多线程加载plist

OpenGl规范:不能在新开的线程中,创建texture,texture必须在主线程创建,通俗点,就是所有的opengl api都必须在主线程中调用;其它的操作,比如文件,

内存,plist等,可以在新线程中做,这个不是cocos2d不支持,是opengl的标准,不管你是在android,还是windows上使用opengl,都是这个原理。

我们可以在新开的线程中,加载资源,设置一个静态变量bool,在新线程中,当加载完所有资源后,设置bool值为真。在主线程中Update中,检测bool值,为假,

可以重绘UI(例如,显示加载图片,或者模拟加载进度),为真,则加载目标场景。相关代码如下:

void* LoadingScene::updateInfo(void* args){       CCSpriteFrameCache *cache = CCSpriteFrameCache::sharedSpriteFrameCache();       cache->addSpriteFramesWithFile("BattleIcons.plist");       cache->addSpriteFramesWithFile("ArcherAnim.plist");       cache->addSpriteFramesWithFile("DeathReaperAnim.plist");       loadComplete = true;  //状态值设为真,表示加载完成       return NULL;  }  
成功加载且运行后,你会发现新场景中所有精灵都不显示(类似于黑屏了)。为什么呢?

因为我们在加载plist文件时,addSpriteFramesWithFile方法里会帮我们创建plist对应Png图的Texture2D,并将其加载进缓存中。可是这里就遇到了一个OpenGl规范的问题:不能在新开的线程中,创建texture,texture必须在主线程创建.通俗点,就是所有的opengl api都必须在主线程中调用;其它的操作,比如文件,内存,plist等,可以在新线程中做,这个不是cocos2d不支持,是opengl的标准,不管你是在android,还是windows上使用opengl,都是这个原理。

所以不能在新线程中创建Texture2D,导致纹理都不显示,那么该怎么办?让我们看看CCSpriteFrameCache源码,发现CCSpriteFrameCache::addSpriteFramesWithFile(const char *pszPlist, CCTexture2D *pobTexture)方法,是可以传入Texture2D参数的。是的,我们找到了解决方法:

int LoadingScene::start(){      CCTexture2D *texture = CCTextureCache::sharedTextureCache()->addImage("BattleIcons.png"); //在这里(主线程中)加载plist对应的Png图片进纹理缓存      CCTexture2D *texture2 = CCTextureCache::sharedTextureCache()->addImage("ArcherAnim.png"); //以这种方法加载的纹理,其Key值就是文件path值,即例如  texture2的key值就是ArcherAnim.png      CCTexture2D *texture3 = CCTextureCache::sharedTextureCache()->addImage("DeathReaperAnim.png");      pthread_create(&pid,NULL,updateInfo,NULL); //开启新线程      return 0;  }  void* LoadingScene::updateInfo(void* args){      CCSpriteFrameCache *cache = CCSpriteFrameCache::sharedSpriteFrameCache();      CCTextureCache* teCache = CCTextureCache::sharedTextureCache();         CCTexture2D* texture1 = teCache->textureForKey("BattleIcons.png"); //从纹理缓存中取出Texure2D,并将其当参数传入addSpriteFramesWithFile方法中      cache->addSpriteFramesWithFile("BattleIcons.plist",texture1);      CCTexture2D* texture2 = teCache->textureForKey("ArcherAnim.png");      cache->addSpriteFramesWithFile("ArcherAnim.plist",texture2);      CCTexture2D* texture3 = teCache->textureForKey("DeathReaperAnim.png");      cache->addSpriteFramesWithFile("DeathReaperAnim.plist",texture3);      loadComplete = true;      return NULL;  }

Tip:OpenGL与线程相结合时,此时你需要把你需要渲染的精灵先加载到内存中去,可以设置成为不显示,然后在线程执行后再设置精灵成显示状态,这样可以解决线程与OpneGL渲染不兼容的问题

主线程负责绘图实现,在分离出来的子线程中完成重计算任务,计算完成后向主线程发回处理完毕的消息,消息是单向流动的,数据从磁盘、网络或其他任何地方

经过处理后最终以视图的形式流向了屏幕。

不同线程间可共享的数据必须是静态的或全局的,因此互斥锁也必须是全局的。