Threads Basics
Learn to create and manage threads for concurrent programming in C++
Learn how to create and manage threads to enable concurrent programming and utilize multiple CPU cores effectively.
A Simple Example
#include <iostream>
#include <thread>
#include <chrono>
void printMessage(const std::string& msg, int count) {
for (int i{0}; i < count; ++i) {
std::cout << msg << " " << i << "\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
int main() {
std::cout << "Main thread starting\n";
// Create thread with function and arguments
std::thread t1{printMessage, "Thread 1", 5};
// Create thread with lambda
std::thread t2{[]{
for (int i{0}; i < 5; ++i) {
std::cout << "Thread 2 " << i << "\n";
std::this_thread::sleep_for(std::chrono::milliseconds(150));
}
}};
// Wait for threads to complete
t1.join();
t2.join();
std::cout << "Main thread ending\n";
return 0;
}
Breaking It Down
#include <thread> - Thread Library
- What it does: Provides the std::thread class for creating and managing threads
- Requires C++11 or newer
- Part of the standard library - no external dependencies needed
- Remember: Link with -pthread flag on Linux/Unix systems
std::thread - Creating Threads
- Syntax: std::thread t{function, arg1, arg2, ...}
- Can pass functions, lambdas, or function objects
- Arguments are copied by default - use std::ref() for references
- Thread starts executing immediately upon creation
join() - Waiting for Thread Completion
- What it does: Blocks the calling thread until the target thread finishes
- Must call join() or detach() before thread object is destroyed
- Calling join() twice causes an exception
- Use joinable() to check if you can call join()
detach() - Independent Execution
- What it does: Separates the thread from the thread object
- Detached threads run independently in the background
- No way to join a detached thread later
- Use for fire-and-forget tasks
Why This Matters
- Modern CPUs have multiple cores that can run code simultaneously.
- Single-threaded programs use only one core, wasting 75-90% of your CPU's power.
- Threading lets you utilize all cores for faster execution - essential for games, servers, data processing, and responsive UIs.
Critical Insight
Creating threads isn't free - there's overhead. For small tasks, the overhead of creating and synchronizing threads exceeds the benefit. Threading helps when tasks are CPU-intensive and can run independently.
Rule of thumb: only use threads when each one will do at least milliseconds of work. For microsecond-level tasks, the threading overhead dominates and slows you down!
Best Practices
Always join or detach: Every thread must be joined or detached before destruction. Forgetting this calls std::terminate().
Use std::thread::hardware_concurrency(): Query the optimal number of threads for your hardware instead of guessing.
Avoid too many threads: Creating thousands of threads causes overhead. Use thread pools for many small tasks.
Pass by value for thread safety: Arguments to std::thread are copied by default, which is safer. Use std::ref() only when necessary.
Common Mistakes
Forgetting to join or detach: A thread must be joined or detached before destruction, or the program terminates.
Data races: Multiple threads accessing the same data without synchronization causes undefined behavior.
Too many threads: Creating thousands of threads overwhelms the scheduler. Use thread pools instead.
Passing references incorrectly: Use std::ref() to pass references to threads, otherwise arguments are copied.
Debug Challenge
This program creates a thread but forgets a critical step. Click the highlighted line to fix it:
Quick Quiz
- What happens if a thread is destroyed without join() or detach()?
- What does std::thread::hardware_concurrency() return?
- Can you copy a std::thread?
Practice Playground
Time to try out what you just learned! Play with the example code below, experiment by making changes and running the code to deepen your understanding.
Output:
Error:
Lesson Progress
- Fix This Code
- Quick Quiz
- Practice Playground - run once