线程基础
std::thread 启动一个执行流。线程函数结束时必须 join(等待)或 detach(分离),否则析构会 std::terminate。
#include <thread>
void worker(int id) { /* ... */ }
std::thread t(worker, 1);
t.join(); // 等待线程结束用 std::jthread(C++20)或封装 RAII 包装,构造启动、析构自动 join,避免忘记 join 导致崩溃。
数据竞争
多个线程无同步地读写同一变量即数据竞争,结果是未定义行为。必须用互斥或原子同步。
int counter = 0;
// 两个线程同时 counter++ —— 危险!
// 实际是 读-改-写 三步,会丢失更新编译加 -fsanitize=thread 用 ThreadSanitizer 自动检测数据竞争,比肉眼排查可靠得多。
互斥与锁
std::mutex 配合 std::lock_guard(RAII 锁)保护临界区,离开作用域自动解锁,异常安全。
std::mutex m;
int counter = 0;
void inc() {
std::lock_guard<std::mutex> lk(m); // 构造加锁
++counter; // 临界区
} // 析构自动解锁只把必须互斥的代码放进临界区,锁内别做 I/O 或耗时操作,否则严重降低并发度。
原子操作
std::atomic 提供无锁的原子读写(如计数器),比互斥轻量。适合简单共享变量,复杂结构仍需锁。
#include <atomic>
std::atomic<int> counter{0};
counter.fetch_add(1, std::memory_order_relaxed); // 原子自增条件变量
std::condition_variable 用于"等待某条件成立",配合 mutex 与 wait/notify 实现线程间协作(如生产者-消费者)。
std::condition_variable cv;
std::mutex m; bool ready = false;
// 等待方
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{ return ready; }); // 原子释放锁并等待
// 通知方
{ std::lock_guard<std::mutex> lk(m); ready = true; }
cv.notify_one();用带谓词的 wait(lk, pred) 防止虚假唤醒导致误判条件。
future 与 async
std::async 启动后台任务并返回 std::future,get() 获取结果(自动等待)。比手动管线程更上层、更易组合。
auto f = std::async(std::launch::async, []{
return heavy_compute();
});
int r = f.get(); // 阻塞直到结果就绪线程安全设计
最好的并发是"不共享":用消息传递(线程各持数据、通过队列通信)或不可变数据,远比加锁省心。必须共享时用清晰的 ownership。
// 每线程独立结果,最后汇总,避免共享
std::vector<std::future<int>> fs;
for (int i = 0; i < 4; ++i)
fs.push_back(std::async([i]{ return compute(i); }));常见陷阱与 FAQ
std::thread 析构时若仍 joinable,程序会 terminate。用 jthread 或在所有路径显式 join/detach。
两个线程各自持有一把锁、又等对方的锁。用 std::lock 一次性锁多把,或固定加锁顺序避免。
不一定。is_lock_free() 可查;大多数基础类型无锁,但某些平台/大小可能回退到内部锁。