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.
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.
0means 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:
hardware_concurrency()asks the C++ runtime for a hardware-concurrency estimate.- Suppose the runtime reports
8. Thendetectedbecomes8. std::max(1u, detected)compares1with8, soworkersbecomes8.- The program prints
Detected: 8andWorkers: 8.
If detection fails:
hardware_concurrency()returns0.detectedbecomes0.std::max(1u, 0)produces1.- 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
-jsetting. - 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.
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 4or 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
| Option | Language / platform | Usually reports | Important behavior |
|---|---|---|---|
std::thread::hardware_concurrency() | C++11+, broadly portable | Logical processors / hardware threads | Returns a hint; may return 0 |
sysconf(_SC_NPROCESSORS_ONLN) | POSIX-like systems, including Linux and many Unix systems | Online logical processors | Check for -1; not ISO C portable |
GetActiveProcessorCount(ALL_PROCESSOR_GROUPS) | Windows | Active logical processors | Suitable for modern Windows processor groups |
sysctlbyname("hw.logicalcpu") | macOS | Logical processors | Apple platform API; query hw.physicalcpu for physical cores |
Logical processors versus physical cores
| Term | Meaning | Example |
|---|---|---|
| Physical core | A physical execution core in the CPU package | 4 physical cores |
| Logical processor | A scheduling target visible to the operating system | 8 logical processors with two threads per core |
| Available processor | A processor the current process is permitted to use | May be less than the machine total in a container |
C versus C++
| Language choice | Portable standard option |
|---|---|
| ISO C | No CPU-count function |
| C++11 and later | std::thread::hardware_concurrency() |
| C or pre-C++11 | Use conditional compilation and native APIs |
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 return0.- 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.
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.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.