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());
}
}