std::thread::hardware_concurrency() returns a hint about how many threads can run concurrently. It may return 0 when that information is unavailable, so provide a fallback rather than relying on a nonzero result. section 2.4
Use the hint to guide thread counts, not as a guarantee. A parallel accumulation example limits threads by both the amount of work and the hardware hint, avoiding unnecessary threads for small ranges. Thread pools also commonly use the returned value to choose their worker count. section 2.4 section 9.1.1
Listing 2.9. A naïve parallel version of std::accumulate
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)
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]);
for(auto& entry: threads)
entry.join();
return std::accumulate(results.begin(),results.end(),init);
}
See how a parallel accumulation example combines the hardware hint with a limit based on input size. section 2.4
Listing 9.1. Simple thread pool
class thread_pool
{
std::atomic_bool done;
threadsafe_queue<std::function<void()> > work_queue; #1
std::vector<std::thread> threads; #2
join_threads joiner; #3
void worker_thread()
{
while(!done) #4
{
std::function<void()> task;
if(work_queue.try_pop(task)) #5
{
task(); #6
}
else
{
std::this_thread::yield(); #7
}
}
}
public:
thread_pool():
done(false),joiner(threads)
{
unsigned const thread_count=std::thread::hardware_concurrency(); #8
try
{
for(unsigned i=0;i<thread_count;++i)
{
threads.push_back(
std::thread(&thread_pool::worker_thread,this)); #9
}
}
catch(...)
{
done=true; #10
throw;
}
}
~thread_pool()
{
done=true; #11
}
template<typename FunctionType>
void submit(FunctionType f)
{
work_queue.push(std::function<void()>(f)); #12
}
};
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":3,\"ch\":75},{\"line\":3,\"ch\":77}],[{\"line\":36,\"ch\":75},{\"line\":36,\"ch\":77}],[{\"line\":42,\"ch\":75},{\"line\":42,\"ch\":77}],[{\"line\":47,\"ch\":75},{\"line\":47,\"ch\":77}],[{\"line\":4,\"ch\":75},{\"line\":4,\"ch\":77}],[{\"line\":5,\"ch\":75},{\"line\":5,\"ch\":77}],[{\"line\":8,\"ch\":75},{\"line\":8,\"ch\":77}],[{\"line\":11,\"ch\":75},{\"line\":11,\"ch\":77}],[{\"line\":13,\"ch\":75},{\"line\":13,\"ch\":77}],[{\"line\":17,\"ch\":75},{\"line\":17,\"ch\":77}],[{\"line\":25,\"ch\":75},{\"line\":25,\"ch\":77}],[{\"line\":31,\"ch\":75},{\"line\":31,\"ch\":77}],[{\"line\":36,\"ch\":75},{\"line\":36,\"ch\":78}],[{\"line\":42,\"ch\":75},{\"line\":42,\"ch\":78}],[{\"line\":47,\"ch\":75},{\"line\":47,\"ch\":78}]]"}
!@%STYLE%@!
See a thread pool that uses the hint to set its worker count. section 9.1.1
