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 is automatically joined
The thread is automatically detached
The thread continues running
  1. What does std::thread::hardware_concurrency() return?
The maximum threads allowed by the OS
A hint about concurrent threads the hardware supports
The number of CPU cores
The current number of running threads
  1. Can you copy a std::thread?
Yes, freely
Yes, but only after calling detach()
No, only move
Yes, with std::copy()

Practice Playground

Try out what you learned! Experiment with the code below.

Lesson Progress

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