Cocos2d-X多线程

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








  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值