std::mutex需配合std::lock_guard等RAII机制显式保护共享变量,票数读写必须加锁,锁粒度最小化至“查+扣”两步,睡眠须在锁外,线程须join/detach,atomic操作需用compare_exchange_weak避免竞态。

std::mutex 不能直接保护全局变量,必须显式加锁解锁
很多人写抢票逻辑时,以为声明一个 std::mutex 全局对象就万事大吉,结果还是出现超卖——因为没在每次访问共享票数前调用 lock() 和 unlock()。更危险的是用 RAII 方式漏掉异常路径:如果临界区里抛异常,没用 std::lock_guard 就可能死锁。
- 必须用
std::lock_guard<:mutex></:mutex>或std::unique_lock包裹临界区,别手写mtx.lock()/mtx.unlock() - 票数变量(比如
int remaining_tickets)不能是裸 int,哪怕只读也要在锁保护下读取,否则可能读到撕裂值(尤其在非原子整型+优化开启时) - 不要把锁粒度设成“整个购票函数”,而是最小化到“检查余票 + 扣减”这两步,否则线程会长时间阻塞
std::this_thread::sleep_for 导致吞吐暴跌,别在临界区里睡
模拟用户随机延迟时,常有人把 std::this_thread::sleep_for 放在锁内,比如“查到有票→睡100ms→扣减”。这会让其他线程等上整整100ms,实际并发能力趋近于单线程。
- 睡眠必须放在锁外:先加锁检查余票,若有则立即扣减并释放锁,再睡;若无则直接释放锁后重试
- 重试策略建议用
std::this_thread::yield()替代短睡眠(如 - 避免用
std::chrono::high_resolution_clock做休眠基准——它不保证单调,某些平台 sleep 可能被大幅拉长
std::atomic 能替代 mutex 吗?看操作是否复合
有人想省事,直接把票数改成 std::atomic<int></int>,用 fetch_sub 扣减。这能避免锁,但掩盖了一个关键问题:扣减成功 ≠ 出票成功。你得先判断是否 >0,再减1——这是两个步骤,atomic 无法原子地完成“条件性递减”。
-
remaining_tickets.load() > 0和remaining_tickets.fetch_sub(1)之间存在竞态:A线程读到1,B也读到1,两者都执行 sub,结果变成 -1 - 正确做法是用
compare_exchange_weak循环尝试:“读当前值 → 若 >0 则 CAS 减1”,但代码变复杂,且失败重试仍需考虑公平性 - 简单场景下,
std::mutex更直观可靠;高频低冲突场景才值得为 atomic 付出额外逻辑成本
std::thread 构造后不 join/detach 会 crash
主线程结束时,所有子线程还在跑,程序直接终止——这不是“抢票失败”,是未定义行为,Linux 下常见 std::system_error: Operation not permitted 或直接 core dump。
立即学习“C++免费学习笔记(深入)”;
- 每个
std::thread对象必须在析构前调用join()或detach();推荐join(),确保所有购票线程完成后再统计结果 - 别在循环里反复创建 thread 对象却不保存句柄,比如
for(...) std::thread{...}.join();—— 临时对象的join()调用无效,析构时仍触发 terminate - 用
std::vector<:thread></:thread>存管理所有线程,最后统一join(),避免遗漏
真正难的不是写多线程,而是确认每处共享访问都被覆盖、每条线程生命周期都被收口、每次 sleep 都没卡在锁里——这些点漏一个,结果就不可重现。


















