Advanced 14 min

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:

1 #include <iostream>
2 #include <thread>
3
4 void work() {
5 std::cout << "Thread working\n";
6 }
7
8 int main() {
9 std::thread t{work};
10 // Missing something here!
11 return 0;
12 }

Quick Quiz

  1. What happens if a thread is destroyed without join() or detach()?
The program calls std::terminate()
The thread continues running
The thread is automatically joined
  1. What does std::thread::hardware_concurrency() return?
The number of CPU cores
A hint about concurrent threads the hardware supports
The current number of running threads
  1. Can you copy a std::thread?
Yes, freely
Yes, with std::copy()
No, only move

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.

Lesson Progress

  • Fix This Code
  • Quick Quiz
  • Practice Playground - run once