线程管理——运行期决定线程数量

hardware_concurrency

 
C++标准库提供了名为std::thread::hardware_concurrency()的函数,该函数将返回能同时并发在一个程序中的线程数量。举例而言,在多核系统返回值为CPU核芯数量;在无法获取系统信息时,该值为0。显然,该API是在运行期决定线程数量的重要提示。


应用实例

 
下述代码实现了一种并行版的accumulate函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
template<typename Iterator,typename T> 
struct accumulate_block {
void operator()(Iterator first,Iterator last,T& result){
result=std::accumulate(first,last,result);
}
};

template<typename Iterator,typename T>
T parallel_accumulate(Iterator first,Iterator last,T init) {
unsigned long const length=std::distance(first,last);
if(!length) // length == 0
return init;
unsigned long const min_per_thread=25; // 线程处理的最小区间
unsigned long const max_threads= (length+min_per_thread-1)/min_per_thread; // 所需线程数
unsigned long const hardware_threads=std::thread::hardware_concurrency(); // 硬件支持线程数
unsigned long const num_threads=std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads);// 实际运行线程数
unsigned long const block_size=length/num_threads;// 实际区间大小
std::vector<T> results(num_threads);
std::vector<std::thread> threads(num_threads-1);
Iterator block_start=first;
for(unsigned long i=0; i < (num_threads-1); ++i) {
Iterator block_end=block_start;
std::advance(block_end,block_size);
threads[i]=std::thread( // 计算每一段
accumulate_block<Iterator,T>(),
block_start,block_end,std::ref(results[i]) // 传入引用
);
block_start=block_end;
}
accumulate_block<Iterator,T>()(block_start,last,results[num_threads-1]); // 计算最后一小段
std::for_each(threads.begin(),threads.end(),std::mem_fn(&std::thread::join)); // 逐一join
return std::accumulate(results.begin(),results.end(),init);
}

识别线程

 
在上个实例中我们使用了循环中设定的变量i作为标识来识别对应的线程。除这种方法以外,C++标准库为每一个线程附加了唯一标识符,其类型为std::thread::id

获取标识符

获取线程标识符有两种方法:

  1. 调用std::thread对象的get_id()成员函数。如果当前对象没有绑定任何执行线程,则返回std::thread::type默认构造值。
  2. 在当前运行线程中使用全局函数std::this_thread::get_id()

标识符性质

std::thread::id可以执行拷贝与对比操作,如果两个对象std::thread::id相等,那它们为同一线程,或者均未绑定任何执行线程。如果不等,则可认为二者绑定的执行线程不同,亦或一个拥有执行线程而另一个没有。

由于std::thread::id可以比对,那么程序开发者可以将其作为有序关联容器的键值,又或者以此作为std::thread对象的排序基准。另外,C++标准库定义了关于std::thread::id的hash函数,因此std::thread::id也可以用于无序关联容器。

使用场景

在实际使用环境下,通常通过std::thread::id识别线程,令它们执行不同操作。举例而言,在并行算法中主线程的任务可能与其他线程不同,为了实现这一需求,我们可以在启动其他线程前存储主线程id,在程序实际运行过程中根据id来分配具体工作任务:

1
2
3
4
5
6
7
std::thread::id master_thread;
void some_core_part_of_algorithm() {
if(std::this_thread::get_id()==master_thread) {
do_master_thread_work();
}
do_common_work();
}