C++ std::thread Primer | join, detach, and Three Mistakes to Avoid

C++ std::thread Primer | join, detach, and Three Mistakes to Avoid

이 글의 핵심

std::thread creation, join and detach, thread safety, mutex/atomic/condition_variable overview, and common pitfalls with fixes.

Introduction: One thread wasn’t enough

“Why so slow?” — I/O wait and single-core usage

Image batch processing ran sequentially—each file: load → resize → save. Disk waits left the CPU idle; 10+ files took 10+ seconds.

With std::thread per file (where safe), wall time dropped by more than half—overlap I/O and CPU.

Cause of single-thread slowness:

  • All work on one thread
  • CPU idle during disk I/O
  • Multi-core machine using one core

After std::thread: parallelize independent file work (watch shared state—see mutex).

You must call join() or detach() before destroying a std::thread—otherwise std::terminate.


Table of contents

  1. What is a thread
  2. Creating std::thread
  3. join and detach
  4. RAII for threads
  5. Thread safety
  6. Mutex basics
  7. condition_variable basics
  8. Atomic basics
  9. jthread and stop_token
  10. Common mistakes
  11. Performance
  12. Best practices
  13. Checklist

1. What is a thread

Process: own address space. Thread: independent execution within a process—shares heap and globals; each has its own stack.

flowchart TB
  subgraph proc["Process (one address space)"]
    main[Main thread]
    t1[Thread 1]
    t2[Thread 2]
    main --- t1
    main --- t2
  end

Concurrent writes to shared variables without synchronization → data race.


2. Creating std::thread

// g++ -std=c++17 -pthread -o thread_hello thread_hello.cpp && ./thread_hello
#include <thread>
#include <iostream>

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

int main() {
    std::thread t(sayHello);  // pass function pointer, not sayHello()
    t.join();
    return 0;
}

Arguments: passed by value to the new thread; use std::ref for references (watch lifetimes).


3. join and detach

  • join(): block until thread finishes—use when you need completion or scoped data must outlive the worker.
  • detach(): thread runs independently—you must ensure all data it touches outlives the thread (no dangling references to stack).

joinable() before double-join.


4. RAII — ThreadGuard / jthread

C++20 std::jthread: joins on destruction; can pass stop_token for cooperative shutdown.


5. Thread safety

Global counter++ from two threads without sync → data race (result often < 2 * iterations).

Fix: mutex or atomic.


6. Mutex basics

std::mutex m;
std::lock_guard<std::mutex> lock(m);
// critical section

7. condition_variable basics

Wait with predicate to handle spurious wakeups: cv.wait(lock, []{ return ready; });


8. Atomic basics

std::atomic<int> counter{0};
counter++;  // no mutex for simple increments

9. jthread + stop_token

Use stop_requested() loops for clean shutdown.


10. Common mistakes

  1. Destroying std::thread without join/detach → terminate
  2. Double join
  3. detach + reference capture to stack locals → use-after-free
  4. Shared mutation without mutex/atomic
  5. std::thread t(sayHello()) — wrong (pass sayHello, not sayHello())

11. Performance

  • Don’t create unbounded threads (e.g. one per file for thousands of files)—use a pool or cap with hardware_concurrency().
  • ThreadSanitizer: -fsanitize=thread

12. Best practices

  • Default to join; detach rarely and only with clear lifetime rules
  • Prefer jthread (C++20) to avoid forgotten joins
  • Protect shared data; minimize lock scope

13. Checklist

  • join or detach before std::thread destruction
  • No detached threads referencing stack
  • Mutex/atomic for shared mutable state
  • joinable() if unsure

  • Mutex
  • Atomics
  • Series index

Keywords

C++ thread, std::thread, join, detach, multithreading, data race, jthread

Summary

Threads share memory—synchronize shared writes. Always join or detach; prefer RAII/jthread; next read mutex.

Next: Mutex and synchronization #7-2

FAQ

A. Link with -pthread: g++ -std=c++17 -pthread -o app app.cpp

join vs detach default?

A. Default join unless you have a long-lived daemon thread and clear lifetime rules for all captured data.


References

  • condition_variable
  • Atomics
  • Mutex
  • Stack vs heap