Heim >Datenbank >MySQL-Tutorial >Cocos2d-x3.3RC0的多线程与异步加载

Cocos2d-x3.3RC0的多线程与异步加载

WBOY
WBOYOriginal
2016-06-07 15:00:561264Durchsuche

1、Cocos2d-x线程与异步介绍 Cocos2d-x是一个单线程的引擎,引擎每一帧之间更新游戏的各元素的状态,以保证它们之间互不干扰,这个过程其实是一个串行的过程,单线程的好处就是无需担心对象更新引起的线程安全问题。但是当使用I/O操作时,单线程的缺点就暴漏

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和thread

1)pthread:互斥体类型为pthread_mutex_t表示,C++11中使用std::mutex表示。上面的代码可以写成下面的样子:

  static int count  = 0; // count 是一个静态全局变量
    /* 保护count操作的互斥体,<span style="font-family: Arial, Helvetica, sans-serif;">PTHREAD_MUTEX_INITIALIZER是对互斥体变量进行初始化的特殊值 </span>*/
    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 <thread>
#include <iostream>
 
 
void callfn(){     ①
    std::cout 代码2是创建thread对象,参数是函数指针callfn,还可以为回调函数提供参数。代码1是回调函数的定义。代码3是讲子线程与主线程合并,使得子线程执行完成后才能继续执行主线程,同时避免了子线程还在执行,主线程已经结束而撤销。
<p><span><br>
</span></p>
<p><span>此外,线程的创建还可以使用堆的方式分配内存,代码如下:</span></p>
<p></p>
<pre class="brush:php;toolbar:false">void callfn(){
    std::cout join();
    delete  t1;             ②
    t1 = nullptr; ③
    return 0;
}
代码1是通过堆分配内存,代码2释放线程对象,代码3防止野指针。

5、声音采用线程预加载示例

#include "cocos2d.h"
#include "SimpleAudioEngine.h"
using 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<void>& 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<asyncstruct>();
        _imageInfoQueue   = new deque<imageinfo>();        
 
        // 创建一个新线程加载纹理
        _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();
}</imageinfo></asyncstruct></void>

void TextureCache::addImageAsyncCallBack(float dt)
{
    // _imageInfoQueue双端队列用来保存在新线程中加载完成的纹理
    std::deque<imageinfo> *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);
        }
    }
}</imageinfo>

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.h"

USING_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 instance
bool 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、运行结果

Cocos2d-x3.3RC0的多线程与异步加载


参考文章:http://blog.csdn.net/u012945598/article/details/41312345

http://blog.csdn.net/tonny_guan/article/details/41017763




Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn