Creating Your First Thread

The std::thread class, defined in <thread>, represents a single thread of execution. To create a thread, you construct a std::thread object with a callable (function, lambda, or functor) to execute.

#include <iostream>
#include <thread>

void hello() {
    std::cout << "Hello from thread!\n";
}

int main() {
    std::thread t(hello);  // Create thread, starts running immediately
    t.join();              // Wait for thread to complete
    return 0;
}

When you construct a std::thread with a callable, the new thread starts executing immediately. It doesn't wait for you to call any "start" method.

The Callable Can Be Anything

std::thread accepts any callable: regular functions, lambdas, function objects, or member functions.

Regular Functions

void do_work() {
    std::cout << "Working...\n";
}

int main() {
    std::thread t(do_work);
    t.join();
}

Lambda Expressions

Lambdas are often the most convenient option:

int main() {
    std::thread t([]() {
        std::cout << "Lambda thread!\n";
    });
    t.join();
}

Function Objects (Functors)

A class with operator() defined:

class Worker {
public:
    void operator()() const {
        std::cout << "Functor thread!\n";
    }
};

int main() {
    Worker w;
    std::thread t(w);  // Passes a copy of w
    t.join();
}
Warning
Watch out for the "most vexing parse" with temporary functors:
// WRONG: Declares a function named t, doesn't create a thread!
std::thread t(Worker());

// Correct alternatives:
std::thread t{Worker()};       // Braced initialization
std::thread t((Worker()));     // Extra parentheses
std::thread t(Worker{});       // Braced temporary

Member Functions

To call a member function on a thread, pass a pointer to the member function and the object:

class Printer {
public:
    void print_message() const {
        std::cout << "Member function thread!\n";
    }
};

int main() {
    Printer p;
    std::thread t(&Printer::print_message, &p);  // Pass pointer to object
    t.join();
}

The &p is required because member functions need a this pointer. You can also pass a reference wrapper or a smart pointer.

You Must Join or Detach

Every std::thread object must be either joined or detached before it is destroyed. If you destroy a std::thread that is still "joinable," the program calls std::terminate() and aborts.

void work() { /* ... */ }

int main() {
    std::thread t(work);
    // Forgot to join or detach!
    // When t is destroyed here, std::terminate() is called
}  // CRASH!

join() - Wait for Completion

Calling join() blocks the calling thread until the thread represented by the std::thread object completes:

std::thread t(work);
// ... do other things ...
t.join();  // Wait here until t finishes
// Now we know the thread is done

After calling join(), the thread is no longer joinable:

std::thread t(work);
t.join();
std::cout << t.joinable() << '\n';  // Prints 0 (false)
t.join();  // ERROR: Can't join a non-joinable thread

detach() - Fire and Forget

Calling detach() separates the thread of execution from the std::thread object. The thread continues running independently, and you lose the ability to wait for it:

std::thread t(work);
t.detach();  // Thread runs in the background
// t is now empty (not joinable)
// We can't wait for the thread anymore

Detached threads are sometimes called daemon threads. They're useful for background tasks that should run for the program's lifetime.

Warning
Detached threads are dangerous! If the main thread exits while a detached thread is still running, the detached thread is abruptly terminated. Any resources it's using may be left in an inconsistent state.
void use_local_variable() {
    int local = 42;
    std::thread t([&local]() {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << local << '\n';  // DANGER: local may be destroyed!
    });
    t.detach();
}  // local is destroyed here, but thread still references it!

Checking if a Thread is Joinable

Use joinable() to check if a thread can be joined or detached:

std::thread t;
std::cout << t.joinable() << '\n';  // 0 (default-constructed, empty)

t = std::thread(work);
std::cout << t.joinable() << '\n';  // 1 (has a running thread)

t.join();
std::cout << t.joinable() << '\n';  // 0 (already joined)

A thread is not joinable if:

  • It was default-constructed (no thread)
  • It has been moved from
  • It has been joined or detached

Moving Threads

std::thread is movable but not copyable. This makes sense - a thread represents a unique system resource.

std::thread t1(work);
// std::thread t2 = t1;       // ERROR: Can't copy
std::thread t2 = std::move(t1);  // OK: Move ownership

// t1 is now empty (not joinable)
// t2 owns the thread
t2.join();

Moving is useful for storing threads in containers or returning them from functions:

std::thread create_thread() {
    return std::thread(work);  // Implicit move
}

int main() {
    std::thread t = create_thread();
    t.join();
}

Managing Multiple Threads

For multiple threads, use a container:

#include <vector>
#include <thread>

void task(int id) {
    std::cout << "Thread " << id << " running\n";
}

int main() {
    std::vector<std::thread> threads;

    // Create 10 threads
    for (int i = 0; i < 10; ++i) {
        threads.emplace_back(task, i);  // Construct in place with argument
    }

    // Join all threads
    for (auto& t : threads) {
        t.join();
    }
}
Best Practice
Use `emplace_back` rather than `push_back` to construct threads directly in the vector, avoiding unnecessary moves.

Exception Safety with RAII

If an exception is thrown between thread creation and join(), the thread will be destroyed without being joined, causing termination:

void risky() {
    std::thread t(work);
    do_something_that_might_throw();  // If this throws...
    t.join();                          // ...we never reach here!
}  // t destroyed while joinable -> terminate()!

The solution is a RAII wrapper that joins in its destructor:

class ThreadGuard {
    std::thread& t_;
public:
    explicit ThreadGuard(std::thread& t) : t_(t) {}

    ~ThreadGuard() {
        if (t_.joinable()) {
            t_.join();
        }
    }

    ThreadGuard(const ThreadGuard&) = delete;
    ThreadGuard& operator=(const ThreadGuard&) = delete;
};

void safe() {
    std::thread t(work);
    ThreadGuard guard(t);  // Will join in destructor

    do_something_that_might_throw();  // Even if this throws...
    // ...guard's destructor will join t
}
Good news
C++20 introduced `std::jthread`, which handles this automatically. We'll cover it later in this chapter.

A Complete Example

#include <iostream>
#include <thread>
#include <vector>
#include <chrono>

void count_to(int n, const std::string& name) {
    for (int i = 1; i <= n; ++i) {
        std::cout << name << ": " << i << '\n';
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

int main() {
    std::cout << "Main: Starting threads\n";

    std::thread alice(count_to, 5, "Alice");
    std::thread bob(count_to, 3, "Bob");

    std::cout << "Main: Threads started, waiting...\n";

    alice.join();
    std::cout << "Main: Alice finished\n";

    bob.join();
    std::cout << "Main: Bob finished\n";

    std::cout << "Main: All done!\n";
    return 0;
}

Possible output (order varies due to scheduling):

Main: Starting threads
Main: Threads started, waiting...
Alice: 1
Bob: 1
Alice: 2
Bob: 2
Alice: 3
Bob: 3
Main: Bob finished
Alice: 4
Alice: 5
Main: Alice finished
Main: All done!