为何该C++多线程程序触发核心转储?是否存在死锁?
C++线程安全演示程序核心转储及无输出问题分析
我编写了一款用于演示线程安全与非线程安全的C++程序,若运行正常则无问题。但实际运行时出现核心转储,且标注为safe的分支无输出。现咨询:该程序为何会触发核心转储?是否存在死锁?
相关代码如下:
#include <iostream> #include <thread> #include<vector> #include <mutex> using namespace std; class Balance { public: int num; explicit Balance(int num) : num(num) {}; std::mutex m; }; void transfer_unsafe(Balance &from, Balance &to, int n) { from.num -= n; to.num += n; } void transfer(Balance &from, Balance &to, int n) { std::unique_lock<std::mutex> lock1(from.m, std::defer_lock); std::unique_lock<std::mutex> lock2(to.m, std::defer_lock); std::lock(from.m, to.m); from.num -= n; to.num += n; } int main() { auto test = [](const string &name, const auto func) { auto start_time = std::chrono::high_resolution_clock::now(); Balance a{100}; Balance b{100}; std::cout << name << std::endl; std::cout << "before " << a.num << " " << b.num << std::endl; std::vector<std::thread> threads; threads.reserve(100); for (int i = 0; i < 1000; i++) { threads.push_back(std::thread{func, std::ref(a), std::ref(b), 1}); } for (int i = 0; i < 1000; i++) { threads.push_back(std::thread{func, std::ref(b), std::ref(a), 1}); } for (auto &t: threads) { t.join(); } std::cout << "after " << a.num << " " << b.num << std::endl; auto end_time = std::chrono::high_resolution_clock::now(); std::cout << "time : " << (end_time - start_time).count() << std::endl; }; test("unsafe", transfer_unsafe); test("safe", transfer); }
核心转储原因
- 非线程安全分支的数据竞争:
transfer_unsafe函数中,多个线程无同步地读写Balance的num成员,属于C++标准中的未定义行为。这种情况下,多个线程同时操作同一内存地址可能导致指令重排、部分写操作等内存混乱问题,直接触发核心转储。 - 线程容器预留空间不足:
threads.reserve(100)仅预留100个线程的空间,但后续创建了2000个线程。虽然std::thread支持移动语义,但vector频繁扩容可能引发不可预测的内存操作问题,加剧核心转储风险。
是否存在死锁?
是的,safe分支存在明确死锁,这也是该分支无输出的核心原因:
在transfer函数中,错误地直接调用std::lock(from.m, to.m),而非传入unique_lock对象管理锁:
lock1和lock2以defer_lock模式创建,并未持有锁;std::lock(from.m, to.m)直接锁住原始互斥量,但未通过unique_lock接管锁的生命周期;- 函数结束时,
lock1和lock2销毁不会解锁互斥量,导致两个互斥量永久锁定。
后续所有尝试获取这两个互斥量的线程都会被阻塞,程序卡在join步骤无法继续执行,自然无法输出后续内容,甚至因资源耗尽触发核心转储。
修复方案
- 修正safe分支的锁管理逻辑:将
std::lock(from.m, to.m)改为传入unique_lock对象,让其自动管理锁的解锁:
void transfer(Balance &from, Balance &to, int n) { std::unique_lock<std::mutex> lock1(from.m, std::defer_lock); std::unique_lock<std::mutex> lock2(to.m, std::defer_lock); std::lock(lock1, lock2); // 由unique_lock接管锁的生命周期 from.num -= n; to.num += n; }
- 调整线程容器预留空间:匹配实际创建的线程数量,避免频繁扩容:
threads.reserve(2000);
- 可选:同步输出操作:添加全局互斥量避免多线程输出混乱:
std::mutex cout_mutex; // 在输出代码处添加锁: std::lock_guard<std::mutex> lock(cout_mutex); std::cout << name << std::endl;
内容的提问来源于stack exchange,提问作者Yunhan Tang
相关产品推荐
相关产品推荐

