Appearance
线程基础:thread / mutex / condition_variable / 生产者消费者
一、std::thread 使用要点
cpp
#include <thread>
void f(int x) { /*...*/ }
std::thread t(f, 1);
t.join(); // 等待;一定要 join 或 detach
// 不 join 不 detach → 析构 std::thread 会 std::terminate 💥- 线程对象在析构前必须 join 或 detach,否则 terminate。
- 传参默认拷贝/按值;想传引用用
std::ref,否则拷贝到线程。
二、互斥锁 mutex + RAII
cpp
std::mutex m;
int cnt = 0;
void inc() {
std::lock_guard<std::mutex> lk(m); // 构造加锁,析构解锁(离开作用域自动解锁)
++cnt;
}lock_guard:简单 RAII,加锁解锁全程作用域。unique_lock:更灵活——可延迟加锁、临时 unlock(配合 condition_variable)、移动。代价略高。- 应避免新裸 lock/unlock 手工管理(异常路径忘解锁)。
避免的误区
- 在循环里反复 lock/unlock。
- 锁的粒度:持锁时间过长会串行化,损害并发。
- 加锁顺序不一致 → 死锁(见死锁篇)。
三、条件变量 condition_variable(重要)
等待某个条件发生再继续。必须与 mutex 配合(阻塞期间要释放锁,允许别人改条件)。
cpp
std::mutex m;
std::condition_variable cv;
std::queue<int> q;
// 生产者 push
{
std::lock_guard<std::mutex> lk(m);
q.push(x);
}
cv.notify_one(); // 唤醒一个等待者
// 消费者
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{ return !q.empty(); }); // 带谓词版:返回时才继续
int v = q.front(); q.pop();为什么 wait 必须配 unique_lock 而不是 lock_guard? 因为 wait 内部要临时释放锁、阻塞、被唤醒后重新加锁;unique_lock 支持 unlock/lock 而 lock_guard 不行。
虚假唤醒(spurious wakeup)/ 如何避免? 等待者可能被无意义唤醒,必须用带谓词的 wait(或 while 循环检查条件),条件不满足继续睡。带谓词 wait 等价于 while(!pred()) cv.wait(lk);。
notify_one唤醒一个;notify_all唤醒全部(多个消费者用)。- 条件变量底层依赖 OS 的 futex/wait-queue。
四、future / promise / async
一次性传值 / 异步调用:
cpp
std::future<int> fu = std::async(std::launch::async, []{ return 42; });
int r = fu.get(); // 阻塞直到结果就绪
std::promise<int> p;
std::future<int> f = p.get_future();
std::thread t([&]{ p.set_value(100); });
int v = f.get(); // 100
t.join();std::async可指定 launch::async(新线程)或 deferred(惰性)。- future 只能 get 一次;多个方等待一个结果用 shared_future。
五、生产-消费者模型(手写高频)
cpp
class SafeQueue {
public:
void push(int v) {
std::unique_lock<std::mutex> lk(m_);
q_.push(v);
cv_.notify_one();
}
int pop() { // 阻塞版
std::unique_lock<std::mutex> lk(m_);
cv_.wait(lk, [&]{ return !q_.empty() || stop_; });
if (q_.empty()) return -1; // 停止信号
int v = q_.front(); q_.pop();
return v;
}
void shutdown() { std::unique_lock<std::mutex> lk(m_); stop_=true; cv_.notify_all(); }
private:
std::queue<int> q_;
std::mutex m_;
std::condition_variable cv_;
bool stop_ = false;
};考察点:谓词 wait(防虚假唤醒 / 防 stop 时卡死)、notify 时机(push 后唤醒)、停止机制(notify_all + 停止标志使等待者退出)、锁粒度(pop/push 均加锁保护 queue)。
六、TSan / 常见并发排查
-fsanitize=thread(ThreadSanitizer)检测数据竞争。- 崩在随机位置、偶发值错乱是典型数据竞争症状,先在 getter/setter 排竞态。
- 锁尽量用 RAII;共享可变权交给“单一写者 + future/mutex”,减少共享。
七、易错点速记
lock_guard不能配 condition_variable.wait。- notify 前应释放锁(避免唤醒了又立刻阻塞)。
- 别用
volatile做线程同步(不保证原子与内存序,见 atomic 篇)。 std::atomic<int>重载++可以,但复合操作如a=a+1若先读后写可能被拆?——fetch_add原子;表达式a=a+1对 atomic 内部是原子的 operator+= 形式,没问题;困难在「读-改-写需要自主原子化」用 CAS。