Cocos2dx学习笔记36 多线程与异步加载

原文地址:http://cn.cocos2d-x.org/tutorial/show?id=1930

1.为什么要使用多线程

Cocos2d-x是一个单线程循环的引擎,引擎通过每一帧之间更新游戏中各元素的状态,以保证它们之间互不干扰,这个过程中尽管看起来成程序像是在并行运行,但实际上却是一个串行过程。单线程的好处就是我们无需去担心游戏对象更新的线程安全问题,但是当我们的程序遇到某些费时的I/O操作时,单线程的缺点也就显现出来了。


举例来说,在游戏进行场景跳转时,通常我们会释放当前场景的资源并加载下一个场景的资源,这样就需要将下一个界面需要用到的纹理加载到内存中,这一过程便需要对资源文件进行读写操作,而这种外部存储操作又十分耗时,如果需要加载的图片量很大并且分辨率很高,就很有可能会造成我们的主线程阻塞,因为处理器无法在如此短暂的时间间隔内(默认帧率1/60)完成这么大的计算量,而程序又因为只有一个线程不会中断当前执行内容去执行其它内容,所以这时候我们变会观察到界面上的帧率骤降甚至界面直接卡住。

为了规避此类问题的发生,Cocos2d-x在引擎中为开发者提供了异步加载的功能,我们可以向TextureCache发送一个异步加载文件的请求,TextureCache内部会帮助我们创建一个新的线程来完成耗时的加载纹理操作,而在我们的主线程中则可以继续执行其他的计算。

除了资源加载之外,网络读写也属于常见的费时操作之一,所以在客户/服务器系统使用线程也是比较常见的现象,例如HttpClient中的异步功能。

2.单核与多核

单核即只有一个处理器,多核即有多个处理器。我们现在的移动设备一般都是双核或四核,如iPhone6、三星note4,较老的设备如iPhone4的cpu则只是单核。这里要说明的是单核多线程与多核多线程之间的差别。

单核设备中的多线程是并发的。

多核设备中的多线程是并行的或并发的。

下面来解释一下这两句话的含义,单核双线程是一种很常见的做法,例如我们编写一段具备多个线程的代码,让它在iphone4上面去运行,由于iphone4只有一个处理器,所以实际上我们所创建的新线程和主线程之间,是一种相互交错运行的状态,比如我们将时间片划分为100毫秒,那么当前的100毫秒内程序执行主线程,下一个100毫秒内程序可能就会去执行另一个线程,再过了100毫秒又回到了主线程,这样做的好处就是,不会让一个线程无期限的延迟,一旦时间片到了程序会强行中断当前的线程而去执行另一个线程。这样在宏观上看起来,它们就像是在同时执行,但事实上,它们仍然是分开执行的。

然而如果这段代码放到了三星note4上去执行,note4具有一个4核的cpu,在这种多处理器的设备上,我们的两个线程可以每个线程霸占一个处理器,并且独立的去执行,即同时运行而不需要去交错运行。这样的状态,我们称之为并行状态。所以,并发实际上是一种伪并行的状态,它只是一种假装在同时执行多个操作的状态。

3.线程安全问题

首先我们来了解一个概念,线程安全。

线程安全是指代码能被多个线程调用而不会产生灾难性的结果。这里我们举个简单的例子来说明(这里笔者使用的是POSIX线程的线程函数格式,理解大概意思就好)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static int count=0; //count是一个静态全局变量
//A方法线程1的线程函数
void *A( *data){
while (1){
count+=1;
printf ( "%d\n" ,count);
}
}
//B方法线程2的线程函数
*B( *data){
(1){
count+=1;
}
}

如上述代码所示,假设我们现在启动了两个线程,两个线程的线程函数分别设为A和B(这里为了便于理解把线程函数分开写了,其实写一个线程函数让两个线程去执行就够了),运行程序后我们期望的控制台输出结果是,123456789.......(这段代码对于实现功能可能没有任意意义....这里只是举个例子说明)

但实际上运行的结果可能不是如此,我们期望的情况是在每个线程函数中,count值做一次加一的操作,然后输出这个count,但是由于不同线程的执行顺序是不可预知的,上述代码很可能出现这样的情况(假设设备是单核):count的初始值是0,现在轮到线程1运行,A中执行到了count +=1,此时count值便已等于1,本来之后应该输出1,但刚好时间片到这里结束了,现在被切换到了线程2,这时候线程2里面的count的值已经为1了,现在再做一次加1,变成了2,然后执行了print语句,输出一个2,然后时间片结束了,又回到了1,1则继续执行刚才没有执行的输出语句,但由于count值已经被线程2再次改变,这时候我们可能就会看到屏幕上的输出结果是 223456789.... 。

当然我说的这个情况未必一定会出现,原因是上文说的,不同线程的执行顺序是不可预知的,每次执行都会产生不同的结果,也许绝大多数情况下输出都是正常的。这个例子只是告诉大家,这样的情况线程就不安全了。

那么如何解决上述问题?

首先,count变量对于两个线程来说,它是一个共享的数据,那么两个线程同时访问这个共享数据就可能会出现问题,就比如之前的那段代码,线程1想要输出的count值是1,但是由于线程2在线程1还没有输出count的时候把这个值给改掉了,但是线程1并不知道这个count值被改掉了然后继续执行输出,结果线程1输出的值就是2。

解决这个问题,最常用的方法就是使线程"同步"。注意,这里的同步指的可不是让线程步调一致的一起运行,我们所说的线程同步,指的是让线程有先后次序的去运行,你先运行完了,我再运行。

使用线程同步最常用的方法就是使相同数据的内存访问"互斥"的进行。用我们上述的例子来解释就是,当线程1里面进行count的加操作以及输出操作时,线程2就不允许访问count,这时候count2只能处于阻塞的状态,等线程1里面对count的操作完成后,线程2才可以访问,一次只允许一个线程去写数据,其它线程只能等待。

举个例子来说,假设我和你各代表一个线程,现在我想要去执行一个操作,上厕所,我在进入厕所之后为了防止你想占用测试,我就会把厕所的门锁上,如果你这个时候也想上厕所,那么就只能在门口等我上完解锁离开后在使用厕所。这里的锁,就好比我们所说的互斥量(互斥体)。我们可以通过对互斥量进行锁定和解除锁定的方式来确保在某一时间段内只有一个线程能够去操作这些数据。

在pthread中的互斥体类型用pthread_mutex_t表示,在C++ 11中可使用std::mutex。

例如刚才的代码可以写成:

15
/*保护count操作的互斥体,<spanstyle="font-family:Arial,sans-serif;">PTHREAD_MUTEX_INITIALIZER是对互斥体变量进行初始化的特殊值</span>*/
pthread_mutex_tcount_mutex=PTHREAD_MUTEX_INITIALIZER;
//A方法线程1的线程函数
*data){
(1){
/*锁定保护count操作的互斥体。*/
pthread_mutex_lock(&count_mutex);
count+=1;
/*已经完成了对count操作的处理,因此解除对互斥体的锁定。*/
pthread_mutex_nlock(&count_mutex);
}
除了互斥体之外,同步工具还有信号量以及条件变量,因为互斥量有时候尽管可以满足我们的需求,但是会浪费很多时间,使用这些工具可以帮助我们实现更复杂的控制模式。

4.Cocos2d-x中使用多线程的注意事项

Cocos2d-x所使用的内存管理机制以及OpenGL的接口函数都不是线程安全的,所以,不要试图在一个除主线程之外的其它线程内调用引擎所提供的内存管理的方法,例如在一个新线程中去创建一个精灵或是层之类的元素,这些元素在create()方法中均会调用autorelease,autorelease以及retain、release都不是线程安全的,OpenGL的上下文也不是线程安全的,所以也不要在新线程中去使用OpenGL的绘制功能。

5.pthread多线程

pthread是一个多线程库,全称是 POSIX 线程,因为它的API遵循国际正式标准POSIX。pthread线程库由C语言开发,可以运行在多个平台上,包括Andoird,iOS,以及Windows,pthread中的所有线程函数和数据类型都在<pthread.h>头文件中声明,也是Cocos2d-x之前推荐使用的多线程库。如今在3.x引入C++11的特性之后,取消了pthread库的引用,我们可以使用标准库thread进行多线程编程。

6.异步加载

笔者的开发环境是Xcode+Cocos2d-x 3.3beta0版本,下面我们来简单的了解一下异步加载的过程。

我们可以使用一个Loading界面很好的实现资源的预加载,只有将资源全部加载到内存后,我们在使用如Sprite,ImageView去创建对象的时候,才不会造成卡顿的现象。那么如何将图片异步加载到内存中,Cocos2d-x为我们提供了addImageAsync()方法,该方法位于TextureCache类中。下面我们来看一下这个方法中所作的工作

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/*异步添加纹理参数为图片的资源路径以及加载完成后进行通知的回调函数*/
TextureCache::addImageAsync( const std::string&path, std::function< (Texture2D*)>&callback)
{
//创建一个纹理对象指针
Texture2D*texture=nullptr;
//获取资源路径
std::stringfullpath=FileUtils::getInstance()->fullPathForFilename(path);
//如果这个纹理已经加载则返回
autoit=_textures.find(fullpath);
if (it!=_textures.end())
texture=it->second; //second为key-value中的value
(texture!=nullptr)
{
//纹理加载过了直接执行回调方法并终止函数
callback(texture);
return ;
}
//第一次执行异步加载的函数时需要对保存消息结构体的队列初始化
(_asyncStructQueue==nullptr)
{
//两个队列的释放会在addImageAsyncCallBack中完成
_asyncStructQueue= new queue<AsyncStruct*>();
_imageInfoQueue= deque<ImageInfo*>();
//创建一个新线程加载纹理
_loadingThread= std:: thread (&TextureCache::loadImage,153)!important">this );
//是否退出变量
_needQuit= false ;
}
(0==_asyncRefCount)
{
/*向Scheduler注册一个更新回调函数
Cocos2d-x会在这个更新函数中检查已经加载完成的纹理
然后每一帧对一个纹理进行处理将这里纹理的信息缓存到TexutreCache中
*/
Director::getInstance()->getScheduler()->schedule(schedule_selector(TextureCache::addImageAsyncCallBack),monospace!important; font-size:1em!important; min-height:inherit!important">);
}
//异步加载纹理数据的数量
++_asyncRefCount;
//生成异步加载纹理信息的消息结构体
AsyncStruct*data= (std:: nothrow )AsyncStruct(fullpath,callback);
//将生成的结构体加入到队列中
_asyncStructQueueMutex.lock();
_asyncStructQueue->push(data);
_asyncStructQueueMutex.unlock();
//将线程解除阻塞表示已有空位置
_sleepCondition.notify_one();
在这段代码中,其中涉及到了一个addImageAsyncCallBack方法,这个方法是用来检查异步加载完成后的纹理的,第一次调用addImageAsync时便会开启:

61
62
63
64
65
66
67
68
69
TextureCache::addImageAsyncCallBack( float dt)
{
//_imageInfoQueue双端队列用来保存在新线程中加载完成的纹理
std::deque<ImageInfo*>*imagesQueue=_imageInfoQueue;
_imageInfoMutex.lock(); //锁定互斥提
(imagesQueue->empty())
{
_imageInfoMutex.unlock(); //队列为空解锁
}
else
{
ImageInfo*imageInfo=imagesQueue->front(); //取出首部元素image信息结构体
imagesQueue->pop_front(); //删除首部元素
//解除锁定
AsyncStruct*asyncStruct=imageInfo->asyncStruct; //获取异步加载的消息结构体
Image*image=imageInfo->image; //获取Image指针用于生成OpenGL纹理贴图
std::string&filename=asyncStruct->filename; //获取资源文件名
//创建纹理指针
Texture2D*texture=nullptr;
//Image指针不为空
(image)
{
//创建纹理对象
texture= )Texture2D();
//由Image指针生成OpenGL贴图
texture->initWithImage(image);
#ifCC_ENABLE_CACHE_TEXTURE_DATA
//cachethetexturefilename
VolatileTextureMgr::addImageTexture(texture,filename);
#endif
//将纹理数据缓存
_textures.insert(std::make_pair(filename,texture));
texture->retain();
//加入到自动释放池
texture->autorelease();
}
else
{
autoit=_textures.find(asyncStruct->filename);
(it!=_textures.end())
texture=it->second;
}
//取得加载完成后需要通知的函数并进行通知
(asyncStruct->callback)
{
asyncStruct->callback(texture);
}
//释放image
(image)
{
image->release();
}
//释放两个结构体
delete asyncStruct;
imageInfo;
//将加载的纹理数量减一
--_asyncRefCount;
/*所有文件加载完毕注销回调函数*/
(0==_asyncRefCount)
{
Director::getInstance()->getScheduler()->unschedule(schedule_selector(TextureCache::addImageAsyncCallBack),monospace!important; font-size:1em!important; min-height:inherit!important">);
}
}
其中还有很多的细节就不具体分析了,加载成功后,我们只需要在之前调用addImageAsync()方法时指定的回调函数中进行进度条百分比的设置就可以了。例如:

22
bool HelloWorld::init()
//////////////////////////////
//1.superinitfirst
(!Layer::init())
{
return ;
}
/*异步加载纹理*/
for ( i=0;i<10;i++){
Director::getInstance()->getTextureCache()->addImageAsync( "HelloWorld.png" ));
}
true ;
}
HelloWorld::imageLoadedCallback(Ref*pSender)
{
//每次成功加载一个纹理就可以在这里回调方法里设置进度条的进度了所有纹理加载完成就跳转界面
异步加载的Demo可以参见Texture2DTest。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


    本文实践自 RayWenderlich、Ali Hafizji 的文章《How To Create Dynamic Textures with CCRenderTexture in Cocos2D 2.X》,文中使用Cocos2D,我在这里使用Cocos2D-x 2.1.4进行学习和移植。在这篇文章,将会学习到如何创建实时纹理、如何用Gimp创建无缝拼接纹
Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@163.com微信公众号:HopToad 欢迎转载,转载标注出处:http://blog.csdn.netotbaron/article/details/424343991.  软件准备 下载地址:http://cn.cocos2d-x.org/download 2.  简介2.1         引用C
第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从Cocos2D-x官网上下载,进入网页http://www.cocos2d-x.org/download,点击Cocos2d-x以下的Download  v3.0,保存到自定义的文件夹2:从python官网上下载。进入网页https://www.python.org/downloads/,我当前下载的是3.4.0(当前最新
    Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发引擎,易学易用,支持多种智能移动平台。官网地址:http://cocos2d-x.org/当前版本:2.0    有很多的学习资料,在这里我只做为自己的笔记记录下来,错误之处还请指出。在VisualStudio2008平台的编译:1.下载当前稳
1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《最强大脑》娱乐节目。将2048改造成一款挑战玩家对数字记忆的小游戏。邮箱:appdevzw@163.com微信公众号:HopToadAPK下载地址:http://download.csdn.net/detailotbaron/8446223源码下载地址:http://download.csdn.net/
   Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试以QtCreatorIDE来进行CMake构建。Cocos2d-x3.X地址:https://github.com/cocos2d/cocos2d-x1.打开QtCreator,菜单栏→"打开文件或项目...",打开cocos2d-x目录下的CMakeLists.txt文件;2.弹出CMake向导,如下图所示:设置
 下载地址:链接:https://pan.baidu.com/s/1IkQsMU6NoERAAQLcCUMcXQ提取码:p1pb下载完成后,解压进入build目录使用vs2013打开工程设置平台工具集,打开设置界面设置: 点击开始编译等待编译结束编译成功在build文件下会出现一个新文件夹Debug.win32,里面就是编译
分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!http://www.captainbed.net前言上次用象棋演示了cocos2dx的基本用法,但是对cocos2dx并没有作深入的讨论,这次以超级马里奥的源代码为线索,我们一起来学习超级马里奥的实
1. 圆形音量button事实上作者的本意应该是叫做“电位计button”。可是我觉得它和我们的圆形音量button非常像,所以就这么叫它吧~先看效果:好了,不多解释,本篇到此为止。(旁白: 噗。就这样结束了?)啊才怪~我们来看看代码:[cpp] viewplaincopyprint?CCContro
原文链接:http://www.cnblogs.com/physwf/archive/2013/04/26/3043912.html为了进一步深入学习贯彻Cocos2d,我们将自己写一个场景类,但我们不会走的太远,凡是都要循序渐进,哪怕只前进一点点,那也至少是前进了,总比贪多嚼不烂一头雾水的好。在上一节中我们建
2019独角兽企业重金招聘Python工程师标准>>>cocos2d2.0之后加入了一种九宫格的实现,主要作用是用来拉伸图片,这样的好处在于保留图片四个角不变形的同时,对图片中间部分进行拉伸,来满足一些控件的自适应(PS: 比如包括按钮,对话框,最直观的形象就是ios里的短信气泡了),这就要求图
原文链接:http://www.cnblogs.com/linji/p/3599478.html1.环境和工具准备Win7VS2010/2012,至于2008v2版本之后似乎就不支持了。 2.安装pythonv.2.0版本之前是用vs模板创建工程的,到vs2.2之后就改用python创建了。到python官网下载版本2.7.5的,然后
环境:ubuntu14.04adt-bundle-linux-x86_64android-ndk-r9d-linux-x86_64cocos2d-x-3.0正式版apache-ant1.9.3python2.7(ubuntu自带)加入环境变量exportANDROID_SDK_ROOT=/home/yangming/adt-bundle-linux/sdkexportPATH=${PATH}:/$ANDROID_SDK_ROOTools/export
1开发背景游戏程序设计涉及了学科中的各个方面,鉴于目的在于学习与进步,本游戏《FlappyBird》采用了两个不同的开发方式来开发本款游戏,一类直接采用win32底层API来实现,另一类采用当前火热的cocos2d-x游戏引擎来开发本游戏。2需求分析2.1数据分析本项目要开发的是一款游
原文链接:http://www.cnblogs.com/linji/p/3599912.html//纯色色块控件(锚点默认左下角)CCLayerColor*ccc=CCLayerColor::create(ccc4(255,0,0,128),200,100);//渐变色块控件CCLayerGradient*ccc=CCLayerGradient::create(ccc4(255,0,0,
原文链接:http://www.cnblogs.com/linji/p/3599488.html//载入一张图片CCSprite*leftDoor=CCSprite::create("loading/door.png");leftDoor->setAnchorPoint(ccp(1,0.5));//设置锚点为右边中心点leftDoor->setPosition(ccp(240,160));/
为了答谢广大学员对智捷课堂以及关老师的支持,现购买51CTO学院关老师的Cocos2d-x课程之一可以送智捷课堂编写图书一本(专题可以送3本)。一、Cocos2d-x课程列表:1、Cocos2d-x入门与提高视频教程__Part22、Cocos2d-x数据持久化与网络通信__Part33、Cocos2d-x架构设计与性能优化内存优
Spawn让多个action同时执行。Spawn有多种不同的create方法,最终都调用了createWithTwoActions(FiniteTimeAction*action1,FiniteTimeAction*action2)方法。createWithTwoActions调用initWithTwoActions方法:对两个action变量初始化:_one=action1;_two=action2;如果两个a
需要环境:php,luajit.昨天在cygwin上安装php和luajit环境,这真特么是一个坑。建议不要用虚拟环境安装打包环境,否则可能会出现各种莫名问题。折腾了一下午,最终将环境转向linux。其中,luajit的安装脚本已经在quick-cocos2d-x-develop/bin/中,直接luajit_install.sh即可。我的lin
v3.0相对v2.2来说,最引人注意的。应该是对触摸层级的优化。和lambda回调函数的引入(嗯嗯,不枉我改了那么多类名。话说,每次cocos2dx大更新。总要改掉一堆类名函数名)。这些特性应该有不少人研究了,所以今天说点跟图片有关的东西。v3.0在载入图片方面也有了非常大改变,仅仅只是