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的自带实例)
这是一个读写文件的例子:
voidrun_pipeline(intnthreads)

...{
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的实现:

classMyInputFilter:publictbb::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

classMyTransformFilter:publictbb::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:

classMyOutputFilter:publictbb::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类:

classMyBuffer...{
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模型的精髓在于隐藏同步到队列边界.
而队列的缓冲作用减少了锁碰撞的负面影响.从而实现高效方便的并发编程模型.
呵呵,先到这里.