并发
异步
缓存
下面将我寻常工作中碰到一些问题例举一二,其设想头脑不过以上三点。
1 使命行列
1.1 以生产者-消费者模子设想使命行列
生产者-消费者模子是人们非常熟习的模子,比方在某个服务器顺序中,当User数据被逻辑模块修正后,就发生一个更新数据库的使命(produce),投递给IO模块使命行列,IO模块从使命行列中掏出使命实行sql操纵(consume)。
设想通用的使命行列,示例代码以下:
细致完成可拜见:
http://ffown.googlecode.com/svn/trunk/fflib/include/detail/task_queue_impl.h
void task_queue_t::produce(const task_t& task_) { lock_guard_t lock(m_mutex); if (m_tasklist->empty()){ //! 前提满足叫醒守候线程 m_cond.signal(); } m_tasklist->push_back(task_); } int task_queue_t::comsume(task_t& task_){ lock_guard_t lock(m_mutex); while (m_tasklist->empty()) //! 当没有功课时,就守候直到前提满足被叫醒{ if (false == m_flag){ return -1; } m_cond.wait(); } task_ = m_tasklist->front(); m_tasklist->pop_front(); return 0; }
1.2 使命行列运用技能
1.2.1 IO 与逻辑星散
比方收集游戏服务器顺序中,收集模块收到音讯包,投递给逻辑层后马上返回,继承接收下一个音讯包。逻辑线程在一个没有io操纵的环境下运转,以保证及时性。示例:
void handle_xx_msg(long uid, const xx_msg_t& msg){ logic_task_queue->post(boost::bind(&servie_t::proces, uid, msg)); }
注重,此形式下为单使命行列,每一个使命行列单线程。
1.2.2 并行流水线
上面的只是完成了io 和 cpu运算的并行,而cpu中逻辑操纵是串行的。在某些场所,cpu逻辑运算部份也可完成并行,如游戏中用户A种菜和B种菜两种操纵是完全能够并行的,由于两个操纵没有同享数据。最简朴的体式格局是A、B相干的操纵被分派到差别的使命行列中。示例以下:
void handle_xx_msg(long uid, const xx_msg_t& msg) { logic_task_queue_array[uid % sizeof(logic_task_queue_array)]->post( boost::bind(&servie_t::proces, uid, msg)); }
注重,此形式下为多使命行列,每一个使命行列单线程。
1.2.3 衔接池与异步回调
比方逻辑Service模块须要数据库模块异步载入用户数据,并做后续处置惩罚盘算。而数据库模块具有一个牢固衔接数的衔接池,当实行SQL的使命到来时,挑选一个余暇的衔接,实行SQL,并把SQL 经由历程回调函数通报给逻辑层。其步骤以下:
预先分派好线程池,每一个线程建立一个衔接到数据库的衔接
为数据库模块建立一个使命行列,一切线程都是这个使命行列的消费者
逻辑层想数据库模块投递sql实行使命,同时通报一个回调函数来接收sql实行效果
示例以下:
void db_t:load(long uid_, boost::functionpost(boost::bind(&db_t:load, uid, func));
注重,此形式下为单使命行列,每一个使命行列多线程。
2. 日记
本文主要讲C++多线程编程,日记体系不是为了进步顺序效力,然则在顺序调试、运转期排错上,日记是无可替换的东西,置信开辟后台顺序的朋侪都邑运用日记。罕见的日记运用体式格局有以下几种:
流式,如logstream << “start servie time[%d]” << time(0) << ” app name[%s]” << app_string.c_str() << endl;
Printf 花样如:logtrace(LOG_MODULE, “start servie time[%d] app name[%s]“, time(0), app_string.c_str());
两者各有优瑕玷,流式是线程平安的,printf花样花样化字符串会更直接,但瑕玷是线程不平安,假如把app_string.c_str() 换成app_string (std::string),编译被经由历程,然则运转期会crash(假如命运运限好每次都crash,命运运限不好偶然会crash)。我个人宠爱printf作风,能够做以下革新:
增添线程平安,应用C++模板的traits机制,能够完成线程平安。示例:
template void logtrace(const char* module, const char* fmt, ARG1 arg1){ boost::format s(fmt); f % arg1; }
如许,除了规范范例+std::string 传入其他范例将编译不能经由历程。这里只列举了一个参数的例子,能够重载该版本支撑更多参数,假如你情愿,能够支撑9个参数或更多。
为日记增添色彩,在printf中到场控制字符,能够再屏幕终端上显现色彩,Linux下示例:printf(“33[32;49;1m [DONE] 33[39;49;0m")
更多色彩计划拜见:
http://hi.baidu.com/jiemnij/blog/item/d95df8c28ac2815cb219a80e.html
每一个线程启动时,都应该用日记打印该线程担任什么功用。如许,顺序跑起来的时刻经由历程top–H–p pid 能够得知谁人功用运用cpu的若干。实际上,我的每行日记都邑打印线程id,此线程id非pthread_id,而其实是线程对应的体系分派的历程id号。
3. 机能监控
只管已有许多东西能够剖析c++顺序运转机能,然则其大部份照样运转在顺序debug阶段。我们须要一种手腕在debug和release阶段都能监控顺序,一方面得知顺序瓶颈之地点,一方面尽早发明哪些组件在运转期涌现了非常。
一般都是运用gettimeofday 来盘算某个函数开支,能够准确到玄妙。能够应用C++确实定性析构,非常轻易的完成猎取函数开支的小东西,示例以下:
struct profiler{ profiler(const char* func_name){ gettimeofday(&tv, NULL); } ~profiler(){ struct timeval tv2; gettimeofday(&tv2, NULL); long cost = (tv.tv_sec - tv.tv_sec) * 1000000 + (tv.tv_usec - tv.tv_usec); //! post to some manager } struct timeval tv; }; #define PROFILER() profiler(__FUNCTION__)
Cost 应该被投递到机能统计管理器中,该管理器定时讲机能统计数据输出到文件中。
4 Lambda 编程
运用foreach 替代迭代器
许多编程言语已内建了foreach,然则c++还没有。所以发起本身在须要遍历容器的处所编写foreach函数。习气函数式编程的人应该会非常钟情运用foreach,运用foreach的优点多若干少有些,如:
http://www.cnblogs.com/chsword/archive/2007/09/28/910011.html
但主如果编程哲学上层面的。
示例:
void user_mgr_t::foreach(boost::function func_){ for (iterator it = m_users.begin(); it != m_users.end() ++it){ func_(it->second); } }
比方要完成dump 接口,不须要重写关于迭代器的代码
void user_mgr_t:dump(){ struct lambda { static void print(user_t& user){ //! print(tostring(user); } }; this->foreach(lambda::print); }
实际上,上面的代码变通的生成了匿名函数,假如是c++ 11 规范的编译器,本能够写的更简约一些:
this->foreach([](user_t& user) {} );
然则我大部份时候编写的顺序都要运转在centos 上,你知道吗它的gcc版本是gcc 4.1.2, 所以大部份时候我都是用变通的体式格局运用lambda函数。
Lambda 函数连系使命行列完成异步
罕见的运用使命行列完成异步的代码以下:
void service_t:async_update_user(long uid){ task_queue->post(boost::bind(&service_t:sync_update_user_impl, this, uid)); } void service_t:sync_update_user_impl(long uid){ user_t& user = get_user(uid); user.update() }
如许做的瑕玷是,一个接口要相应的写两遍函数,假如一个函数的参数变了,那末另一个参数也要随着修正。而且代码也不是很雅观。运用lambda能够让异步看起来更直观,似乎就是在接口函数中马上完成一样。示例代码:
void service_t:async_update_user(long uid){ struct lambda { static void update_user_impl(service_t* servie, long uid){ user_t& user = servie->get_user(uid); user.update(); } }; task_queue->post(boost::bind(&lambda:update_user_impl, this, uid)); }
如许当要修正该接口时,直接在该接口内修正代码,非常直观。
5. 奇技淫巧
应用 shared_ptr 完成 map/reduce
Map/reduce的语义是先将使命分别为多个使命,投递到多个worker中并发实行,其发生的效果经reduce汇总后生成终究的效果。Shared_ptr的语义是什么呢?当末了一个shared_ptr析构时,将会挪用托管对象的析构函数。语义和map/reduce历程非常邻近。我们只需本身完成讲要求分别多个使命即可。示例历程以下:
定义要求托管对象,到场我们须要在10个文件中搜刮“oh nice”字符串涌现的次数,定义托管构造体以下:
struct reducer{ void set_result(int index, long result) { m_result[index] = result; } ~reducer(){ long total = 0; for (int i = 0; i < sizeof(m_result); ++i){ total += m_result[i]; } //! post total to somewhere } long m_result[10]; };
定义实行使命的 worker
void worker_t:exe(int index_, shared_ptr ret) { ret->set_result(index, 100); }
将使命支解后,投递给差别的worker
shared_ptr ret(new reducer()); for (int i = 0; i < 10; ++i) { task_queue[i]->post(boost::bind(&worker_t:exe, i, ret)); }
以上就是C++ 多线程编程总结的内容,更多相干内容请关注ki4网(www.ki4.cn)!