优化中多线程并发访问

2015年03月23日 11:52 0 点赞 0 评论 更新于 2025-11-21 18:15

多线程并发访问在Cocos2d - x引擎中的情况

在Cocos2d - x引擎里,多线程并发访问的应用并不广泛。这主要是由于该引擎的整体结构设计未采用多线程。其源自Objective - C的Ref对象,需借助AutoreleasePool进行内存管理,而AutoreleasePool并非线程安全的。所以,不建议在子多线程中调用Ref对象的retain()release()autorelease()等函数。此外,OpenGL上下文对象同样不支持线程安全。

不过,有时我们需要异步加载一些资源,例如加载图片纹理、进行声音的预处理以及发起网络请求数据等。若要异步加载图片纹理,可参考《Cocos2d - x优化中纹理优化》。但对于声音的预处理和网络请求数据等操作,则需自行通过多线程技术来实现。

Cocos2d - x引擎也提供了多线程技术。在Cocos2d - x 3.x之前,使用的是第三方的pthread技术;Cocos2d - x 3.x之后,采用了C++11新规范中的std::thread多线程技术,该技术使用起来较为简单。

std::thread多线程技术

std::thread是C++11引入的一个新线程库,它提供了线程管理相关的函数。同时,std::thread库中还提供了std::mutex(互斥量),借助std::mutex能够实现线程同步。

启动新线程

启动一个新线程十分简单。当创建一个std::thread对象时,它会自动启动。创建std::thread对象时,可以为其提供该线程的回调函数。以下代码展示了如何创建线程以及实现线程函数的回调:

#include <iostream>
#include <thread>

// 回调函数定义
void callfn() {
std::cout << "Hello thread! " << std::endl;
}

int main() {
// 创建t1线程对象,参数为函数指针callfn
std::thread t1(callfn);
// 将子线程与主线程合并
t1.join();
return 0;
}

上述代码中,第std::thread t1(callfn);行创建了t1线程对象,其参数为函数指针callfn。若有需要,还能为回调函数提供参数。void callfn()是回调函数的定义。t1.join()这行代码的作用是将子线程与主线程合并,这样能确保子线程执行完毕后,主线程才会继续执行,避免出现子线程仍在执行,而主线程已结束并撤销的情况。

使用堆方式分配内存创建线程

创建线程还可以采用堆的方式分配内存,代码如下:

#include <iostream>
#include <thread>

void callfn() {
std::cout << "Hello thread! " << std::endl;
}

int main() {
// 通过堆方式分配内存创建动态线程对象
std::thread* t1 = new std::thread(callfn);
t1->join();
// 释放对象
delete t1;
// 设置指针变量,防止“野指针”
t1 = nullptr;
return 0;
}

上述代码中,std::thread* t1 = new std::thread(callfn);行通过new运算符以堆方式分配内存,创建了动态线程对象。由于是动态分配的对象,使用完成后需要释放。delete t1;语句用于释放对象,释放完成后,通过t1 = nullptr设置指针变量,可防止出现“野指针”。

异步预处理声音

std::thread线程在Cocos2d - x中有诸多实际应用,如异步预处理声音、异步加载资源文件等。虽然Cocos2d - x为异步加载图片纹理提供了API,但声音的异步加载需要我们自行实现。下面介绍如何异步预处理声音。

在之前的介绍中,声音预处理是同步进行的,这会导致主线程阻塞,使用户感觉卡顿。若卡顿时间较长,为解决主线程阻塞问题、改善用户体验,可采用异步预处理声音的方式。

在前面的案例中,采用std::thread线程进行异步预处理声音,可在AppDelegate中进行异步加载。修改后的AppDelegate.h代码如下:

#include "cocos2d.h"
#include "SimpleAudioEngine.h"
using namespace CocosDenshion;

class AppDelegate : private cocos2d::Application {
private:
// 声明私有的std::thread线程指针变量
std::thread *_loadingAudioThread;
// 声明私有的异步预处理声音函数
void loadingAudio();
public:
AppDelegate();
virtual ~AppDelegate();
// 其他代码...
};

修改后的AppDelegate.cpp代码如下:

#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");
}

上述代码中,_loadingAudioThread = new std::thread(&AppDelegate::loadingAudio, this);行在构造函数里创建了线程对象,此代码也可放置到AppDelegate::applicationDidFinishLaunching()函数中,可根据实际需求在合适的地方创建。_loadingAudioThread->join()用于将线程合并到主线程,该处理一般在线程处理完成后调用,可在析构函数中调用,也可在一些退出函数(如Layer的onExit函数)中调用。由于_loadingAudioThread是动态对象指针类型,需要释放对象,可通过CC_SAFE_DELETE(_loadingAudioThread)实现。CC_SAFE_DELETE宏的作用等同于以下代码:

delete _loadingAudioThread;
_loadingAudioThread = nullptr;

void AppDelegate::loadingAudio()定义了线程回调函数,在该函数中进行声音的预处理操作。

作者信息

menghao

menghao

共发布了 3994 篇文章