C++ Multithreading & Concurrency

std::thread and join discipline, mutex and lock_guard, condition variables, atomics, thread pools and common data races.

AdvancedUpdated 2026-10-2948K views

Threads and join

#include <thread>
#include <iostream>

void worker(int id) {
    std::cout << "worker " << id << '\n';
}

int main() {
    std::thread t1(worker, 1);
    std::thread t2(worker, 2);

    t1.join();                // 等待结束(必须 join 或 detach)
    t2.join();                // 否则 std::terminate
    // RAII 封装:join 在析构自动执行
    return 0;
}
A thread that is neither joined nor detached before destruction calls std::terminate and kills the process. Wrap threads in a joining guard or always join at scope exit.

Mutex and Condition Variables

#include <mutex>
#include <condition_variable>
#include <queue>

std::mutex m;
std::condition_variable cv;
std::queue<Task> q;

// 生产者
void push(Task t) {
    {
        std::lock_guard lk(m);     // 离开作用域解锁
        q.push(std::move(t));
    }
    cv.notify_one();               // 唤醒一个等待者
}

// 消费者
Task take() {
    std::unique_lock lk(m);        // 配合 cv 需可解锁
    cv.wait(lk, [] { return !q.empty(); });   // 谓语防虚假唤醒
    Task t = std::move(q.front());
    q.pop();
    return t;
}

Prefer lock_guard for plain critical sections and unique_lock only when you need to unlock within the block (condition variables, deferred locks). Never lock two mutexes in different orders across threads — that is a textbook deadlock.

Atomics

#include <atomic>

std::atomic<long> counter{0};       // 无锁计数

void hit() {
    counter.fetch_add(1, std::memory_order_relaxed);   // 不需要顺序保证
}

std::atomic<bool> ready{false};

// 发住 once 或广播
ready.store(true, std::memory_order_release);   // 发布数据
while (!ready.load(std::memory_order_acquire));  // 看到 ready 即可见数据

// 指针/标志的 CAS 循环:无锁结构基础
current.compare_exchange_strong(expected, desired);

Relaxed for counters, acquire/release for publishing data, seq_cst (default) when in doubt. A wrong memory order compiles and passes tests while failing on other CPUs.

Thread Pools in Practice

  • Creating a thread per task is wasteful at high QPS — size the pool once, typically hardware_concurrency() for CPU-bound work.
  • Hand-rolled pools are fine to learn, but production uses asio, Intel TBB or a vetted header-only pool.
  • Use thread_local for per-thread caches (connections, scratch buffers) to avoid contention.
  • Profile with perf or VTune: lock contention, not computation, is usually the bottleneck.

Friends & Partners