Skip to content
Oğuzhan Düğüncü edited this page Dec 11, 2025 · 1 revision

🚀 Modern C++ Threading Guide

A practical and deeply technical guide to std::thread, ownership semantics, move rules, false sharing, cache-line behavior, and high-performance concurrency patterns in C++.

⚡ Production-level explanations, clean diagrams, and benchmark-backed insights.

📚 Contents

  • Introduction
  • Thread Lifecycle & Constructors
  • Ownership Model
  • Join vs Detach
  • Move Semantics (The Dangerous Parts)
  • False Sharing & Cache Lines
  • Measuring Read vs Write Cost
  • Safe Reuse Patterns
  • Unsafe Patterns (Terminate Scenarios)
  • Cheat Sheet

1️⃣ Introduction

Modern C++ threads (std::thread) give you native, low-level control over OS threads with a strict ownership model:

  • Threads are move-only
  • They must be joined or detached
  • Incorrect ownership transfer → std::terminate()
  • Performance is heavily impacted by memory layout and cache-line behavior

2️⃣ Thread Lifecycle & Constructors

✔ Start a thread

std::thread t(func, arg1, arg2);

✔ Default constructor

std::thread t;
  • joinable() == false

❌ No copy constructor

std::thread t2 = t; // compile error

✔ Move constructor

std::thread t2(std::move(t));

3️⃣ Ownership Model

A std::thread owns its OS thread.

  • Ownership is unique
  • Must be released via join/detach

4️⃣ Join vs Detach

join()

  • Waits for completion
  • joinable() becomes false

detach()

  • Thread runs independently
  • joinable() becomes false

Destructor rule

If a thread is joinable during destruction → terminate.

5️⃣ Move Semantics (Danger Zone)

✔ Safe move assignment

std::thread t;
t = std::thread(func);

❌ Unsafe move assignment

std::thread t(func);
t = std::thread(func2); // terminate()

Move assignment checks:

if (this->joinable()) → terminate()

6️⃣ False Sharing & Cache Lines

False sharing occurs when multiple threads write to variables on the same cache line.

Example (bad)

struct room { int id, size; };
room rooms[N];

8 objects * 8 bytes = 64 bytes → same cache line.

Solution: Padding

struct alignas(64) padded_room {
    int id;
    int size;
    char pad[56];
};

Your benchmark:

  • Normal write: ~4.6s
  • Padded write: ~1.1s
  • Speedup: 4.1×

7️⃣ Measuring Read vs Write Cost

Reading is cheap.
Writing is expensive—especially when cores write to the same cache line.

Your experiments confirmed:

  • Read-only: fast
  • Write-only: slow unless padded

8️⃣ Safe Reuse Patterns

std::thread worker;

worker = std::thread(func1);
worker.join();

worker = std::thread(func2);
worker.join();

9️⃣ Unsafe Patterns

std::thread t(func);
t = std::thread(func2); // terminate
{
    std::thread t(func);
} // terminate
std::thread t(func);
t = std::move(t); // terminate

🔟 Cheat Sheet

✔ Safe

  • std::thread t; t = std::thread(f);
  • t.join();
  • t.detach();
  • Move constructor

❌ Unsafe

  • Move assignment to joinable thread
  • Destructor with joinable thread
  • Self-move assignment

🧪 Cache-Line Calculation

offset  = address & 0x3F;   // 0–63
line_id = address >> 6;