Question

Find CPU Cores in C and C++ Across Platforms

cppcmultithreadingmultiplatform

Question

Is there a platform-independent way in C or C++ to determine how many CPU cores a machine has? If not, how can this information be obtained on Windows, Unix-like systems, and macOS?

Also, does the reported value represent physical CPU cores or logical processors (such as Hyper-Threading threads)?

Short Answer

You will learn that standard C has no portable CPU-count API, while C++11 provides std::thread::hardware_concurrency() as a convenient cross-platform hint. You will also learn platform-specific options for Windows, Linux/Unix, and macOS, plus the important difference between physical cores and logical processors.

Concept

There is no ISO C function that reports the number of CPU cores. Hardware and operating-system details are outside the scope of the C standard library.

C++11 added:

std::thread::hardware_concurrency()

This function returns an estimate of the number of hardware execution threads available to the program. In practice, that is usually the number of logical processors, not necessarily the number of physical CPU cores.

For example, a processor with 4 physical cores and 2 hardware threads per core may report 8 logical processors.

This distinction matters because programs often use the value to choose a worker-thread count. Creating one worker per logical processor can be reasonable for CPU-bound work, but it is not always optimal. The process may also be restricted by containers, CPU affinity, quotas, or operating-system scheduling policies.

Treat CPU-count APIs as useful runtime information, not as a guarantee that your program should create exactly that many threads.

Mental Model

Think of a CPU as an office building:

  • A physical core is a worker at a desk.
  • A logical processor is a work lane the operating system can schedule work onto.
  • Hyper-Threading or simultaneous multithreading can give one physical worker multiple work lanes.

Most convenient APIs count the lanes, not the workers. If an API says 8, the machine might have eight physical cores, or it might have four cores with two logical processors each.

Also, even if the building has eight lanes, your program may be allowed to use only some of them. A container or CPU-affinity setting can limit the lanes available to your process.

Take Quiz

Syntax and Examples

In portable C++11 or later, include <thread> and call std::thread::hardware_concurrency():

#include <iostream>
#include <thread>

int main() {
    unsigned int logicalProcessors = std::thread::hardware_concurrency();

    if (logicalProcessors == 0) {
        std::cout << "CPU count could not be determined\n";
    } else {
        std::cout << "Logical processors: " << logicalProcessors << '\n';
    }
}

hardware_concurrency() returns an unsigned int.

  • A nonzero result is an implementation-provided hint.
  • 0 means the implementation could not determine a value.
  • The result commonly represents logical processors.

When choosing a worker count, always provide a fallback:

#include <algorithm>
#include <thread>

unsigned int workerCount() {
    unsigned int count = std::thread::hardware_concurrency();
    return std::max(1u, count);
}

For older C++ versions or C programs, use operating-system APIs behind conditional compilation.

Step by Step Execution

Consider this program:

#include <algorithm>
#include <iostream>
#include <thread>

int main() {
    unsigned int detected = std::thread::hardware_concurrency();
    unsigned int workers = std::max(1u, detected);

    std::cout << "Detected: " << detected << '\n';
    std::cout << "Workers: " << workers << '\n';
}

Step by step:

  1. hardware_concurrency() asks the C++ runtime for a hardware-concurrency estimate.
  2. Suppose the runtime reports 8. Then detected becomes 8.
  3. std::max(1u, detected) compares 1 with 8, so workers becomes 8.
  4. The program prints Detected: 8 and Workers: 8.

If detection fails:

  1. hardware_concurrency() returns 0.
  2. detected becomes 0.
  3. std::max(1u, 0) produces 1.
  4. The application still has a safe worker count instead of creating zero workers.

Real World Use Cases

CPU-count detection is commonly used to make software adapt to the machine it runs on:

  • Image and video processing: divide independent frames or image tiles among worker threads.
  • Build tools: compile independent source files in parallel, similar to a -j setting.
  • Data processing: process chunks of logs, CSV files, or records concurrently.
  • Scientific computing: split simulations or numerical calculations into independent tasks.
  • Game engines: configure job-system worker pools.
  • Server software: choose a starting size for a CPU-bound background-worker pool.

For I/O-heavy work, such as waiting for network responses or disk reads, the best thread count may be higher than the CPU count—or an asynchronous design may be more appropriate.

Take Quiz

Real Codebase Usage

In real projects, developers usually read hardware concurrency once during startup and use it as a configurable default.

A common C++ pattern is a fallback plus a user-defined limit:

#include <algorithm>
#include <thread>

unsigned int defaultWorkerCount(unsigned int configuredMaximum) {
    unsigned int detected = std::thread::hardware_concurrency();
    unsigned int available = detected == 0 ? 1u : detected;

    return std::min(available, configuredMaximum);
}

Important production patterns include:

  • Configuration override: let users provide --threads 4 or a configuration value. Hardware detection should be a default, not an unchangeable rule.
  • Validation: reject a configured value of zero when at least one worker is required.
  • Capping: avoid creating an excessive number of threads for small workloads.
  • Task queues: create a fixed pool of workers and submit many small jobs, rather than creating one thread per item.
  • Testing: allow a fixed worker count so tests are repeatable.
  • Container awareness: when possible, prefer APIs that account for the CPUs available to the current process rather than blindly using total machine hardware.

A useful rule is: use CPU count to select a reasonable initial value, then measure and tune the actual workload.

Common Mistakes

Assuming the result is physical-core count

unsigned int cores = std::thread::hardware_concurrency();

The variable name is misleading. The result generally refers to logical processors. Prefer a name such as logicalProcessors or hardwareThreads.

Assuming the function can never return zero

Broken approach:

std::vector<std::thread> workers(std::thread::hardware_concurrency());

If the function returns 0, the program creates no workers. Use a fallback:

unsigned int count = std::max(1u, std::thread::hardware_concurrency());

Creating one thread per input item

for (const auto& file : files) {
    std::thread(processFile, file).detach();
}

This can create thousands of threads. Use a fixed-size thread pool or task queue instead.

Treating the count as a performance guarantee

More threads do not always mean faster execution. Threads add scheduling, synchronization, memory, and cache overhead. Benchmark realistic workloads.

Ignoring CPU restrictions

A virtual machine, container, or CPU-affinity setting can limit a process to fewer CPUs than exist on the host. Platform-specific APIs and runtime environments may report different values depending on what they measure.

Using sysconf without checking for errors

On Unix-like systems, sysconf returns -1 on error. Do not convert that directly to an unsigned value.

Comparisons

OptionLanguage / platformUsually reportsImportant behavior
std::thread::hardware_concurrency()C++11+, broadly portableLogical processors / hardware threadsReturns a hint; may return 0
sysconf(_SC_NPROCESSORS_ONLN)POSIX-like systems, including Linux and many Unix systemsOnline logical processorsCheck for -1; not ISO C portable
GetActiveProcessorCount(ALL_PROCESSOR_GROUPS)WindowsActive logical processorsSuitable for modern Windows processor groups
sysctlbyname("hw.logicalcpu")macOSLogical processorsApple platform API; query hw.physicalcpu for physical cores

Logical processors versus physical cores

TermMeaningExample
Physical coreA physical execution core in the CPU package4 physical cores
Logical processorA scheduling target visible to the operating system8 logical processors with two threads per core
Available processorA processor the current process is permitted to useMay be less than the machine total in a container

C versus C++

Language choicePortable standard option
ISO CNo CPU-count function
C++11 and laterstd::thread::hardware_concurrency()
C or pre-C++11Use conditional compilation and native APIs
Take Quiz

Cheat Sheet

// C++11+: portable hardware-concurrency hint
#include <thread>
unsigned int n = std::thread::hardware_concurrency();
// Always use a fallback when at least one worker is needed.
#include <algorithm>
unsigned int workers = std::max(1u, std::thread::hardware_concurrency());
/* POSIX / Unix-like: online logical processors */
#include <unistd.h>
long n = sysconf(_SC_NPROCESSORS_ONLN);
if (n < 1) {
    /* handle failure */
}
/* macOS: logical or physical CPU count */
#include <sys/types.h>
#include <sys/sysctl.h>

int count = 0;
size_t size = sizeof(count);
sysctlbyname("hw.logicalcpu", &count, &size, NULL, 0);
/* Use "hw.physicalcpu" for physical-core count. */
// Windows: active logical processors
#include <windows.h>
DWORD n = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);

Rules:

  • std::thread::hardware_concurrency() is a hint and can return 0.
  • The common result is logical processors, not guaranteed physical cores.
  • Use CPU count as a default worker-pool size, then make it configurable.
  • For CPU-bound work, start near the available logical-processor count and benchmark.
  • For I/O-bound work, CPU count alone is not a good thread-count rule.

FAQ

Is std::thread::hardware_concurrency() platform-independent?

Yes. It is part of standard C++11 and later. Its exact result is implementation-defined and may be 0 if unknown.

Does hardware_concurrency() return physical cores?

Usually, no. It typically returns the number of logical processors available or known to the implementation. Hyper-Threading can make this larger than the physical-core count.

How do I get the number of CPU cores in standard C?

You cannot do this with ISO C alone. Use an operating-system-specific API, such as sysconf on POSIX systems or Windows system-information APIs.

What does _SC_NPROCESSORS_ONLN mean?

It asks a POSIX system for the number of logical processors that are currently online. This is usually the useful value for scheduling worker threads.

How can I get physical CPU cores on macOS?

Use sysctlbyname("hw.physicalcpu", ...). Use hw.logicalcpu when you need logical processors instead.

Should my program create one thread for every logical processor?

It is a reasonable starting point for CPU-bound jobs, but not a universal rule. Consider workload size, synchronization cost, CPU limits, and user configuration.

Why might CPU detection return zero in C++?

The C++ standard permits hardware_concurrency() to return 0 when the runtime cannot determine the value. Code should handle that case.

Can a container change the reported CPU count?

Yes. Containers, virtual machines, CPU affinity, and quotas can affect the processors a process can use or the value reported by a particular API.

Take Quiz

Mini Project

Description

Build a small C++ command-line utility that reports the C++ runtime's hardware-concurrency hint and selects a safe default number of worker threads. This mirrors the startup logic used by command-line tools and task-processing applications.

Goal

Print the detected logical-processor hint and a usable worker count that is never zero.

Requirements

Use std::thread::hardware_concurrency() to detect hardware concurrency.

Handle the case where detection returns 0.

Accept an optional command-line worker-count override.

Reject an override of 0.

Print whether the selected count came from detection or user configuration.

Take Quiz

Keep learning

Related questions

View more