liveBook · Manning

Std::thread::hardware_concurrency

As explained in C++ Concurrency in Action, Second Edition

A C++ standard library function that returns a hint about the number of hardware threads available for concurrent execution.

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

How C++ Concurrency in Action, Second Edition covers it

How deep it goes
Defines the return as a hint, notes that it may be zero, and demonstrates its use without detailing implementation internals.
Examples
Uses a parallel std::accumulate implementation and a thread pool to demonstrate thread-count choices.
What you can do after
Apply it via the C++ standard library to guide a thread count, while supplying a fallback for a zero result.
How it connects
Connects to parallel accumulation, std::accumulate, thread pools, and oversubscription.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of C++ Concurrency in Action, Second Edition. The section links point to the parts of the book it draws on.