1、进程线程的基本概念
教科书上说:进程是资源分配的最小单位,线程是CPU调度的最小单位。
进程是程序在计算机上的一次执行活动。直观的讲就是会产生一个pid。
int main()
{
//业务逻辑代码
return 0;
}
当进入main函数时就会创建一个进程。进程是计算机上的一个代码片段。
线程是可执行代码的可分派单元。线程包含在进程当中,线程把一个进程分为很多片,每一片都可以是一个独立的流程。
为什么使用多线程和多进程:
为了实现并发执行,就是在同一时间同时执行多条任务。这样可以提高程序效率。
在cocos2d-x中使用多线程:
游戏中的界面体验很重要,最基本的条件就是流程性,并且在cocos2d开发的游戏中,每帧都要刷新一下界面,按60fps算,每秒刷新60次。所以可以把耗时的操作放到其他线程中,就不会对界面线程造成影响。
2、使用std::thread实现多线程
cocos2d-x 3.0系列版本使用了c++11,C++11提供了一个更好的用于线程操作的类std::thread,std::thread 在 <thread> 头文件中声明,因此使用 std::thread 时需要包含 <thread> 头文件。std::thread 常用构造函数:(有4个)
1、默认构造函数:thread()默认构造函数,创建一个空的 thread 执行对象
std::thread t1;//
2、 初始化构造函数:template <class Fn, class... Args>
初始化构造函数,创建一个 thread对象,该 thread对象可被 joinable,新产生的线程会调用 fn 函数,该函数的参数由 args 给出,参数数量不确定。
std::thread t2(f1, 10);
常用函数:
1、get_id
获取当前线程 ID。
2、joinable
检查线程是否可被 join,上面初始化创建的线程默认是joinable的。
3、join
Join 线程。待子线程执行完之后,主线程才可以继续执行下去,此时主线程会释放掉执行完后的子线程资源。当调用join线程是,原先的joinable变为false。
4、detach
Detach 线程
将子线程从主线程里分离,子线程执行完成后会自己释放掉资源。分离后的线程,主线程将对它没有控制权了。主线程和子线程互相没有关系,没有控制权。
代码清单:
#ifndef __TestThread_SCENE_H__
#define __TestThread_SCENE_H__
#include "cocos2d.h"
USING_NS_CC;
class TestThread : 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();
Label *lblInit;//声明一个label指针变量
void threadA(int first,std::string second);//声明一个回调函数
// implement the "static create()" method manually
CREATE_FUNC(TestThread);
};
#endif // __TestThread_SCENE_H__
#include "TestThread.h"
#include <thread>
Scene* TestThread::createScene()
{
// 'scene' is an autorelease object
auto scene = Scene::create();
// 'layer' is an autorelease object
auto layer = TestThread::create();
// add layer as a child to scene
scene->addChild(layer);
// return the scene
return scene;
}
// on "init" you need to initialize your instance
bool TestThread::init()
{
//
// 1. super init first
if ( !Layer::init() )
{
return false;
}
Size size = Director::getInstance()->getVisibleSize();
lblInit = Label::create("hi","Arial",24);//创建一个标签
addChild(lblInit);
lblInit->setPosition(size/2);
std::thread t1(&TestThread::threadA,this,10,"I am a string");//创建一个线程,使用初始化构造函数创建线程,回调函数要取他的地址,参数是整型和字符串
t1.detach();//主线程和子线程互不影响,主线程还会往下执行。子线程不阻塞主线程的运行。
lblInit->setString("main thread");
return true;
}
void TestThread::threadA(int first,std::string second)
{
std::this_thread::sleep_for(std::chrono::seconds(3));//设置等待时间
lblInit->setString("threadA");
//log("threadA id 0x%x",std::this_thread::get_id());//输出子线程的id,这个log函数会一直阻塞主线程的运行,加了这个之后不会回到主线程
log("threadA first = %d,second = %s",first,second.c_str());//因此只有使用detach时,用log函数才不会阻塞线程
}
3、线程的互斥量和线程锁
多个线程同时访问共享资源时,经常会出现冲突等。为了避免这种情况的发生,可以使用互斥量,当一个线程锁住了互斥量后,其他线程必须等待这个互斥量解锁后才能访问它。
thread提供了四种不同的互斥量:
1、独占式互斥量non-recursive (std::mutex)
独占式互斥量加解锁是成对的,同一个线程内独占式互斥量在没有解锁的情况下,再次对它进行加锁这是不对的,会得到一个未定义行为。
2、递归式互斥量recursive (std::recursive_mutex)
与独占式互斥量不同的是,同一个线程内在互斥量没有解锁的情况下可以再次进行加锁,不过他们的加解锁次数需要一 b致,递归式互斥量我们平时可能用得比较少些。
3、允许超时的独占式互斥量non-recursive that allows timeouts on the lock functions(std::timed_mutex)
用法同独占式互斥量,如果线程1对共享资源的访问时间比较长,这时线程2可能等不了那么久,所以设置一个超时时间,在到了超时时间时如果线程1中的互斥量还没有解锁,线程2就不等了,继续往下执行。
4、允许超时的递归式互斥量recursive mutex that allows timeouts on the lock functions (std::recursive_timed_mutex)
下面还是以外卖为例演示下互斥量和线程锁的用法:假如有50份蛋炒饭,两个下单窗口同时运作,蛋炒饭卖完为止。
#ifndef __TestMutex_SCENE_H__
#define __TestMutex_SCENE_H__
#include "cocos2d.h"
USING_NS_CC;
class TestMutex : public cocos2d::Layer
{
int total;//总的蛋炒饭数量
int selled;//卖出去的蛋炒饭数量
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();
void threadA();//创建两个线程,代表两个买饭窗口
void threadB();
// implement the "static create()" method manually
CREATE_FUNC(TestMutex);
};
#endif // __TestMutex_SCENE_H__
#include "TestMutex.h" #include <thread> std::mutex _mutex;//创建一个独占式互斥变量 Scene* TestMutex::createScene() { // 'scene' is an autorelease object auto scene = Scene::create(); // 'layer' is an autorelease object auto layer = TestMutex::create(); // add layer as a child to scene scene->addChild(layer); // return the scene return scene; } // on "init" you need to initialize your instance bool TestMutex::init() { // // 1. super init first if ( !Layer::init() ) { return false; } total = 50; selled = 0; std::thread t1(&TestMutex::threadA,this); t1.detach();//两个线程互不影响 std::thread t2(&TestMutex::threadB,this); t2.detach(); return true; } void TestMutex::threadA() { while (selled<total)//用循环模拟不停的下订单 { _mutex.lock();//加锁之后,在锁之内的代码执行过程中,运行权限不会跳转到另一个线程 selled++; std::this_thread::sleep_for(std::chrono::seconds(3));//加线程等待的操作 log("threadA %d",selled); _mutex.unlock(); } } void TestMutex::threadB() { while (selled<total) { _mutex.lock(); selled++; std::this_thread::sleep_for(std::chrono::seconds(3)); log("threadB %d",selled); _mutex.unlock(); } }
4、线程安全
在使用多线程时,总会遇到线程安全的问题。
为了防止线程和线程之间有干扰,才有了线程安全的概念。
cocos2dx 3.0系列中新加入了一个专门处理线程安全的函数performFunctionInCocosThread(),他是Scheduler类的一个成员函数:void Scheduler::performFunctionInCocosThread(const std::function<void ()> &function)//参数是回调函数,这个函数需要在县城中进行调用,函数提要放到cocosthread中定义当在其他线程中调用cocos2d的函数时使用该函数。使用这个函数就能安全的在其他线程中去控制cocos2dx的一些操作了。比如在worker线程中对精灵的创建等操作可能会没有用,在performFunctionInCocosThread就能很容易实现。比如:void thread_fun(){ log("new thread create:t_id:0x%x",GetCurrentThreadId()); Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,this] { for(int i=0;i<=1000;i++){} log("[performFunctionInCocosThread] finished!"); });
#ifndef __TestThreadSafe_SCENE_H__ #define __TestThreadSafe_SCENE_H__ #include "cocos2d.h" USING_NS_CC; class TestThreadSafe : 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(); void threadA();//创建线程回调函数 // implement the "static create()" method manually CREATE_FUNC(TestThreadSafe); }; #endif // __TestThreadSafe_SCENE_H__
#include "TestThreadSafe.h" #include <thread> Scene* TestThreadSafe::createScene() { // 'scene' is an autorelease object auto scene = Scene::create(); // 'layer' is an autorelease object auto layer = TestThreadSafe::create(); // add layer as a child to scene scene->addChild(layer); // return the scene return scene; } // on "init" you need to initialize your instance bool TestThreadSafe::init() { // // 1. super init first if ( !Layer::init() ) { return false; } std::thread t1(&TestThreadSafe::threadA,this);//创建子线程 t1.detach();//与主线程互不影响 return true; } void TestThreadSafe::threadA()//定义线程回调函数,在这里创建一个精灵,添加到游戏中 { Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,this]{//使用C++的匿名函数方式 auto sprite = Sprite::create("HelloWorld.png");//创建一个精灵 addChild(sprite); Size size = Director::getInstance()->getWinSize();//获取屏幕的大小 sprite->setPosition(size/2);//设置精灵的位置 }); //如果不采用上面的回调函数形式,精灵是添加不进去的,在子线程中没法对UI线程进行操作。 }
5、在子线程中进行网络请求
做过android开发的小伙伴们应该知道,新版本的android系统已经不允许在UI线程中进行网络请求了,必须新建一个线程。本节课以此为例,在worker线程中进行网络请求,演示下多线程在游戏中的使用方式。把网络请求的部分放在其他线程当中。
#ifndef __TestThreadHttp_SCENE_H__ #define __TestThreadHttp_SCENE_H__ #include "cocos2d.h" #include "network\HttpRequest.h" #include "network\HttpClient.h" #include "network\HttpResponse.h" USING_NS_CC; using namespace cocos2d::network; class TestThreadHttp : 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(); void theadA();//添加线程 void complete(HttpClient *client,HttpResponse *response); // implement the "static create()" method manually CREATE_FUNC(TestThreadHttp); }; #endif // __TestThreadHttp_SCENE_H__
#include "TestThreadHttp.h" #include <thread> Scene* TestThreadHttp::createScene() { // 'scene' is an autorelease object auto scene = Scene::create(); // 'layer' is an autorelease object auto layer = TestThreadHttp::create(); // add layer as a child to scene scene->addChild(layer); // return the scene return scene; } // on "init" you need to initialize your instance bool TestThreadHttp::init() { // // 1. super init first if ( !Layer::init() ) { return false; } std::thread t1(&TestThreadHttp::theadA,this); t1.detach(); return true; } void TestThreadHttp::theadA() { auto request = new HttpRequest(); request->setUrl("http://httpbin.org/ip"); request->setTag("type get"); //设置请求类型 request->setRequestType(HttpRequest::Type::GET); char data[50] = "data"; request->setRequestData(data,strlen(data)); request->setResponseCallback(CC_CALLBACK_2(TestThreadHttp::complete,this)); //创建httpclinet对象 auto client = HttpClient::getInstance(); client->setTimeoutForConnect(60); client->setTimeoutForRead(100); client->send(request); request->release(); } void TestThreadHttp::complete(HttpClient *client,HttpResponse *response)//对服务器返回数据做响应的糊掉函数 { log("request tag is:%s",response->getHttpRequest()->getTag()); log("response code is:%d",response->getResponseCode()); if(response->isSucceed()) { /*std::vector<char> * data = response->getResponseData(); log("response data is:"); for (int i = 0; i < data->size(); i++) { log("%c",(*data)[i]); }*/ std::vector<char> * data = response->getResponseData(); std::stringstream oss; for (int i = 0; i < data->size(); i++) { oss<<(*data)[i]; } std::string str = oss.str(); log("response data is:%s",str.c_str()); //在场景中添加精灵 auto sprite = Sprite::create("HelloWorld.png"); addChild(sprite); Size size = Director::getInstance()->getWinSize(); sprite->setPosition(size/2); }else { log("error msg is:%s",response->getErrorBuffer()); } }