C++多线程编程总结实例讲解Word下载.docx
- 文档编号:16845430
- 上传时间:2022-11-26
- 格式:DOCX
- 页数:13
- 大小:21.74KB
C++多线程编程总结实例讲解Word下载.docx
《C++多线程编程总结实例讲解Word下载.docx》由会员分享,可在线阅读,更多相关《C++多线程编程总结实例讲解Word下载.docx(13页珍藏版)》请在冰豆网上搜索。
comsume(task_t&
task_){
while
empty())//!
当没有作业时,就等待直到条件满足被唤醒{
(false
==m_flag){
return
-1;
m_cond.wait();
task_=m_tasklist->
front();
pop_front();
0;
1.2
任务队列使用技巧
1.2.1IO与逻辑分离
比如网络游戏服务器程序中,网络模块收到消息包,投递给逻辑层后立即返回,继续接受下一个消息包。
逻辑线程在一个没有io操作的环境下运行,以保障实时性。
示例:
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相关的操作被分配到不同的任务队列中。
示例如下:
msg){
logic_task_queue_array[uid%sizeof(logic_task_queue_array)]->
post(
boost:
注意,此模式下为多任务队列,每个任务队列单线程。
1.2.3连接池与异步回调
比如逻辑Service模块需要数据库模块异步载入用户数据,并做后续处理计算。
而数据库模块拥有一个固定连接数的连接池,当执行SQL的任务到来时,选择一个空闲的连接,执行SQL,并把SQL通过回调函数传递给逻辑层。
其步骤如下:
∙n
预先分配好线程池,每个线程创建一个连接到数据库的连接
为数据库模块创建一个任务队列,所有线程都是这个任务队列的消费者
逻辑层想数据库模块投递sql执行任务,同时传递一个回调函数来接受sql执行结果
示例如下:
db_t:
load(long
uid_,boost:
function<
(user_data_t&
)func_){
//!
sqlexecute,constructuser_data_tuser
func_(user)
process_user_data_loaded(user_data_t&
){
todosomething
db_task_queue->
load,uid,func));
注意,此模式下为单任务队列,每个任务队列多线程。
2.日志
本文主要讲C++多线程编程,日志系统不是为了提高程序效率,但是在程序调试、运行期排错上,日志是无可替代的工具,相信开发后台程序的朋友都会使用日志。
常见的日志使用方式有如下几种:
流式,如logstream<
<
"
startservietime[%d]"
<
time(0)<
appname[%s]"
app_string.c_str()<
endl;
Printf格式如:
logtrace(LOG_MODULE,"
startservietime[%d]appname[%s]"
time(0),app_string.c_str());
二者各有优缺点,流式是线程安全的,printf格式格式化字符串会更直接,但缺点是线程不安全,如果把app_string.c_str()换成app_string(std:
string),编译被通过,但是运行期会crash(如果运气好每次都crash,运气不好偶尔会crash)。
我个人钟爱printf风格,可以做如下改进:
增加线程安全,利用C++模板的traits机制,可以实现线程安全。
template<
typename
ARG1>
logtrace(const
char*module,const
char*fmt,ARG1arg1){
boost:
formats(fmt);
f%arg1;
这样,除了标准类型+std:
string传入其他类型将编译不能通过。
这里只列举了一个参数的例子,可以重载该版本支持更多参数,如果你愿意,可以支持9个参数或更多。
为日志增加颜色,在printf中加入控制字符,可以再屏幕终端上显示颜色,Linux下示例:
printf("
\033[32;
49;
1m[DONE]\033[39;
0m"
)
更多颜色方案参见:
hi.baidu./jiemnij/blog/item/d95df8c28ac2815cb219a80e.html
每个线程启动时,都应该用日志打印该线程负责什么功能。
这样,程序跑起来的时候通过top–H–ppid可以得知那个功能使用cpu的多少。
实际上,我的每行日志都会打印线程id,此线程id非pthread_id,而其实是线程对应的系统分配的进程id号。
3.性能监控
尽管已经有很多工具可以分析c++程序运行性能,但是其大部分还是运行在程序debug阶段。
我们需要一种手段在debug和release阶段都能监控程序,一方面得知程序瓶颈之所在,一方面尽早发现哪些组件在运行期出现了异常。
通常都是使用gettimeofday来计算某个函数开销,可以精确到微妙。
可以利用C++的确定性析构,非常方便的实现获取函数开销的小工具,示例如下:
struct
profiler{
profiler(const
char*func_name){
gettimeofday(&
tv,NULL);
~profiler(){
timevaltv2;
tv2,NULL);
long
cost=(tv.tv_sec-tv.tv_sec)*1000000+(tv.tv_usec-tv.tv_usec);
posttosomemanager
timevaltv;
};
#definePROFILER()profiler(__FUNCTION__)
Cost应该被投递到性能统计管理器中,该管理器定时讲性能统计数据输出到文件中。
4Lambda编程
使用foreach代替迭代器
很多编程语言已经建了foreach,但是c++还没有。
所以建议自己在需要遍历容器的地方编写foreach函数。
习惯函数式编程的人应该会非常钟情使用foreach,使用foreach的好处多多少少有些,如:
.cnblogs./chsword/archive/2007/09/28/910011.html
但主要是编程哲学上层面的。
user_mgr_t:
foreach(boost:
(user_t&
)>
func_){
for
(iteratorit=m_users.begin();
it!
=m_users.end()++it){
func_(it->
second);
比如要实现dump接口,不需要重写关于迭代器的代码
dump(){
lambda{
static
print(user_t&
user){
print(tostring(user);
this->
foreach(lambda:
print);
实际上,上面的代码变通的生成了匿名函数,如果是c++11标准的编译器,本可以写的更简洁一些:
this->
foreach([](user_t&
user){});
但是我大部分时间编写的程序都要运行在centos上,你知道吗它的gcc版本是gcc4.1.2,所以大部分时间我都是用变通的方式使用lambda函数。
Lambda函数结合任务队列实现异步
常见的使用任务队列实现异步的代码如下:
service_t:
async_update_user(long
uid){
task_queue->
sync_update_user_impl,this,uid));
sync_update_user_impl(long
user_t&
user=get_user(uid);
user.update()
这样做的缺点是,一个接口要响应的写两遍函数,如果一个函数的参数变了,那么另一个参数也要跟着改动。
并且代码也不是很美观。
使用lambda可以让异步看起来更直观,仿佛就是在接口函数中立刻完成一样。
示例代码:
update_user_impl(service_t*servie,long
user=servie->
get_user(uid);
user.update();
lambda:
update_user_impl,this,uid));
这样当要改动该接口时,直接在该接口修改代码,非常直观。
5.奇技淫巧
利用shared_ptr实现map/reduce
Map/reduce的语义是先将任务划分为多个任务,投递到多个worker中并发执行,其产生的结果经reduce汇总后生成最终的结果。
Shared_ptr的语义是什么呢?
当最后一个shared_ptr析构时,将会调用托管对象的析构函数。
语义和map/reduce过程非常相近。
我们只需自己实现讲请求划分多个任务即可。
示例过程如下:
定义请求托管对象,加入我们需要在10个文件中搜索“ohnice”字符串出现的次数,定义托管结构体如下:
reducer{
set_result(int
index,long
result){
m_result[index]=result;
~reducer(){
total=0;
(int
i=0;
i<
sizeof(m_result);
++i){
total+=m_result[i];
posttotaltosomewhere
m_result[10];
定义执行任务的worker
worker_t:
exe(int
index_,shared_ptr<
reducer>
ret){
ret->
set_result(index,100);
将任务分割后,投递给不同的worker
shared_ptr<
ret(new
reducer());
10;
++i)
{
task_queue[i]->
exe,i,ret));
C++多线程编程简单实例
分类:
Windows2012-04-1716:
43
3698人阅读
评论(6)
收藏
举报
C++本身并没有提供任何多线程机制,但是在windows下,我们可以调用SDKwin32api来编写多线程的程序,下面就此简单的讲一下:
创建线程的函数
HANDLECreateThread(
LPSECURITY_ATTRIBUTESlpThreadAttributes,//SD
SIZE_TdwStackSize,
//initialstacksize
LPTHREAD_START_ROUTINElpStartAddress,
//threadfunction
LPVOIDlpParameter,
//threadargument
DWORDdwCreationFlags,
//creationoption
LPDWORDlpThreadId
//threadidentifier
);
在这里我们只用到了第三个和第四个参数,第三个参数传递了一个函数的地址,也是我们要指定的新的线程,第四个参数是传给新线程的参数指针。
eg1:
#include<
iostream>
windows.h>
usingnamespacestd;
DWORDWINAPIFun(LPVOIDlpParamter)
while
(1){cout<
"
Fundisplay!
endl;
}
intmain()
HANDLEhThread=CreateThread(NULL,0,Fun,NULL,0,NULL);
CloseHandle(hThread);
maindisplay!
return0;
我们可以看到主线程(main函数)和我们自己的线程(Fun函数)是随机地交替执行的,但是两个线程输出太快,使我们很难看清楚,我们可以使用函数
VOIDSleep(
DWORDdwMilliseconds
//sleeptime
来暂停线程的执行,dwMilliseconds表示千分之一秒,所以
Sleep(1000);
表示暂停1秒
eg2:
{
Sleep(1000);
Sleep(2000);
执行上述代码,这次我们可以清楚地看到在屏幕上交错地输出Fundisplay!
和maindisplay!
,我们发现这两个函数确实是并发运行的,细心的读者可能会发现我们的程序是每当Fun函数和main函数输出容后就会输出换行,但是我们看到的确是有的时候程序输出换行了,有的时候确没有输出换行,甚至有的时候是输出两个换行。
这是怎么回事?
下面我们把程序改一下看看:
eg3:
\n"
;
我们再次运行这个程序,我们发现这时候正如我们预期的,正确地输出了我们想要输出的容并且格式也是正确的。
下面我就来讲一下此前我们的程序为什么没有正确的运行。
多线程的程序时并发地运行的,多个线程之间如果公用了一些资源的话,我们并不能保证这些资源都能正确地被利用,因为这个时候资源并不是独占的,举个例子吧:
eg4:
加入有一个资源inta=3
有一个线程函数selfAdd()该函数是使a+=a;
又有一个线程函数selfSub()该函数是使a-=a;
我们假设上面两个线程正在并发欲行,如果selfAdd在执行的时候,我们的目的是想让a编程6,但此时selfSub得到了运行的机会,所以a变成了0,等到selfAdd的到执行的机会后,a+=a,但是此时a确是0,并没有如我们所预期的那样的到6,我们回到前面EG2,在这里,我们可以把屏幕看成是一个资源,这个资源被两个线程所共用,加入当Fun函数输出了Fundisplay!
后,将要输出endl(也就是清空缓冲区并换行,在这里我们可以不用理解什么事缓冲区),但此时main函数确得到了运行的机会,此时Fun函数还没有来得及输出换行就把CPU让给了main函数,而这时main函数就直接在Fundisplay!
后输出maindisplay!
,至于为什么有的时候程序会连续输出两个换行,读者可以采用同样的分析方法来分析,在这里我就不多讲了,留给读者自己思考了。
那么为什么我们把eg2改成eg3就可以正确的运行呢?
原因在于,多个线程虽然是并发运行的,但是有一些操作是必须一气呵成的,不允许打断的,所以我们看到eg2和eg3的运行结果是不一样的。
那么,是不是eg2的代码我们就不可以让它正确的运行呢?
答案当然是否,下面我就来讲一下怎样才能让eg2的代码可以正确运行。
这涉及到多线程的同步问题。
对于一个资源被多个线程共用会导致程序的混乱,我们的解决方法是只允许一个线程拥有对共享资源的独占,这样就能够解决上面的问题了。
HANDLECreateMutex(
LPSECURITY_ATTRIBUTESlpMutexAttributes,
//SD
BOOLbInitialOwner,
//initialowner
LPCTSTRlpName
//objectname
该函数用于创造一个独占资源,第一个参数我们没有使用,可以设为NULL,第二个参数指定该资源初始是否归属创建它的进程,第三个参数指定资源的名称。
HANDLEhMutex=CreateMutex(NULL,TRUE,"
screen"
这条语句创造了一个名为screen并且归属于创建它的进程的资源
BOOLReleaseMutex(
HANDLEhMutex
//handletomutex
);
该函数用于释放一个独占资源,进程一旦释放该资源,该资源就不再属于它了,如果还要用到,需要重新申请得到该资源。
申请资源的函数如下
DWORDWaitForSingleObject(
HANDLEhHandle,
//handletoobject
//time-outinterval
第一个参数指定所申请的资源的句柄,第二个参数一般指定为INFINITE,表示如果没有申请到资源就一直等待该资源,如果指定为0,表示一旦得不到资源就返回,也可以具体地指定等待
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C+ 多线程 编程 总结 实例 讲解