未来的多线程软件模型--TBB pipeline 模型简介

TBB提供几个并发模型中,最与众不同的是pipeline模型.

Intel曾经在NP IXP 2400平台上实现过pipeline模型.不过是硬件实现.

这次TBB是纯软件实现.

对于这种模型,优点和缺点都很明显.

优点: 模块划分清晰简介,代码量少,便于单个模块测试.在硬件线程多于32个系统中,pipeline是最高效的.因为,当硬件线程多了以后,锁冲突的可能性大大增加,pipeline模型将临界资源分而治之的思想,可以最小化锁冲突.

缺点:在硬件线程少于4个的时候,效率不及常见的模型.

基于以上两点,可以说pipeline是未来多核(>32)软件的主流模型.

说了这麽多废话,还是来看看到底什么是Pipeline.

从名字上,就可以看出Pipeline是借鉴于工业上的流水线模型,将一个功能(大于模块级的功能),分解成多个独立的阶段,不同阶段间通过队列传递产品.这样子,对于一些CPU密集和IO密集的应用,通过Pipeline模型,我们可以把CPU密集stage放在一个filter, 將IO密集stage放在另外一个filter.两个filter可以分配不同的线程,通过连接两者的队列匹配两者的速度差异,从而达到最好的并发效率.

听上去有点负责吧?

不过,TBB帮我们做了很多自动化的操作,上面提到的线程和队列都是不需要使用者去关心的.只需要简单的几个步骤就好.下面来看一个例子:(源自Intel的TBB的自带实例)

这是一个读写文件的例子:

void run_pipeline( int nthreads)
... {
FILE
*input_file=fopen(InputFileName,"r");

FILE
*output_file=fopen(OutputFileName,"w");

//创建一个pipeline
tbb::pipelinepipeline;

//创建文件读stage并添加到pipeline
MyInputFilterinput_filter(input_file);
pipeline.add_filter(input_filter);

//创建处理stage并添加到pipeline
MyTransformFiltertransform_filter;
pipeline.add_filter(transform_filter);

//创建文件写stage并添加到pipeline
MyOutputFilteroutput_filter(output_file);
pipeline.add_filter(output_filter);

//运行pipeline
tbb::tick_countt0=tbb::tick_count::now();
pipeline.run(MyInputFilter::n_buffer);
tbb::tick_countt1
=tbb::tick_count::now();


pipeline.clear();

fclose(output_file);
fclose(input_file);

}

下面看一下文件读stage的实现:

class MyInputFilter: public tbb::filter ... {
public:
staticconstsize_tn_buffer=8;//读文件缓冲块个数
MyInputFilter(FILE*input_file_);
private:
FILE
*input_file;
size_tnext_buffer;
charlast_char_of_previous_buffer;//溢出标志,buffer[0][-1]访问,
MyBufferbuffer[n_buffer];//就是一个捡简单的buffer类,这里不是重点
/**//*override*/void*operator()(void*);//当前stage的运行入口,注意,参数是void*,这个参数是上一级的输出,
//在这里读文件是第一个stage,所以不需要处理参数.
}
;

MyInputFilter::MyInputFilter(FILE
* input_file_):
filter(
/**/ /*is_serial=*/ true ), // 这里指定当前的stage是否可以并行化,true表示串行
next_buffer( 0 ),
input_file(input_file_),
last_char_of_previous_buffer(
' ' )
... {
}


void * MyInputFilter:: operator ()( void * ) ... {
MyBuffer
&b=buffer[next_buffer];
next_buffer
=(next_buffer+1)%n_buffer;//循环使用文件读缓冲区
size_tn=fread(b.begin(),1,b.max_size(),input_file);
if(!n)...{
//文件结束
returnNULL;
}
else...{
b.begin()[
-1]=last_char_of_previous_buffer;//设置溢出标志,这是一个小技巧
last_char_of_previous_buffer=b.begin()[n-1];//保存当前块的最后一个字符
b.set_end(b.begin()+n);
return&b;
}

}

注意:这里的[-1]下标就是访问数组前一个单元的意思,这个不是重点.

可以看到,读文件操作是串行的在一个stage里完成的.

再来看看转换stage:

// 将每个单词的第一个字母转换成大小,文件中不能有中文.j
class MyTransformFilter: public tbb::filter ... {
public:
MyTransformFilter();
/**//*override*/void*operator()(void*item);//这个item正是上一个stage的返回值,MyBuffer对象的指针
}
;

MyTransformFilter::MyTransformFilter():
tbb::filter(
/**/ /*is_serial_=*/ false )
... {} // false标志可以并行的stage

/**/ /*override*/ void * MyTransformFilter:: operator ()( void * item) ... {
//这个函数是可并发的,但是却没有显示的锁,
//这就是pipeline模式的精髓
//同步被隐藏在队列操作中,只要不操作外部数据结构,
//在只操作void*的前提下,无需显示同步处理
MyBuffer&b=*static_cast<MyBuffer*>(item);
intprev_char_is_space=b.begin()[-1]=='';
for(char*s=b.begin();s!=b.end();++s)...{
if(prev_char_is_space&&islower(*s))//转换首字母到大写
*s=toupper(*s);
prev_char_is_space
=isspace((unsignedchar)*s);
}

return&b;//继续传给下一个stage
}

最后是写文件的stage:

class MyOutputFilter: public tbb::filter ... {
FILE
*my_output_file;
public:
MyOutputFilter(FILE
*output_file);
/**//*override*/void*operator()(void*item);
}
;

MyOutputFilter::MyOutputFilter(FILE
* output_file):
tbb::filter(
/**/ /*is_serial=*/ true ), // 串行的
my_output_file(output_file)
... {
}


void * MyOutputFilter:: operator ()( void * item) ... {
MyBuffer
&b=*static_cast<MyBuffer*>(item);
fwrite(b.begin(),
1,b.size(),my_output_file);
returnNULL;
}

MyBuffer类:

class MyBuffer ... {
staticconstsize_tbuffer_size=10000;
char*my_end;
//!storage[0]保存前一个buffer的最后一个字符
charstorage[1+buffer_size];
public:
//!Pointertofirstcharacterinthebuffer
char*begin()
...{
returnstorage+1;//跳过[0]
}

constchar*begin()const...{returnstorage+1;}
//!最后一个字符的下一个位置
char*end()const...{returnmy_end;}
//!设置my_end
voidset_end(char*new_ptr)...{my_end=new_ptr;}
//!最大容量
size_tmax_size()const...{returnbuffer_size;}
//!已经保存的字符数
size_tsize()const...{returnmy_end-begin();}
}
;

可以看出, Pipeline模型的思想是面向 步骤(stage)的 ,不同的stage封装成不同的filter可以灵活的动态的安装在流水线上.实现了高可拆卸性.

在效率方面, Pipeline模型的精髓在于隐藏同步到队列边界.

而队列的缓冲作用减少了锁碰撞的负面影响.从而实现高效方便的并发编程模型.

呵呵,先到这里.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值