Linux与Windows环境下线程池代码运行差异及段错误排查
问题:线程池代码在Linux下触发段错误,Windows运行正常的原因分析
现象描述
Windows环境运行结果:
Start A的地址:xxxxxxxxxxxxxx Deal A的地址:xxxxxxxxxxxxxx Read A的地址:xxxxxxxxxxxxxxLinux环境运行结果:
Start A的地址:xxxxxxxxxxxxxx Deal A的地址:xxxxxxxxxxxxxx segmentation fault
相关代码
template<typename T> class SafeQueue { public: bool empty() { shared_lock<shared_mutex>lc(_m); return _que.empty(); } auto size() { shared_lock<shared_mutex>lc(_m); return _que.size(); } void push(T& t) { unique_lock<shared_mutex> lc(_m); _que.push(t); } bool pop(T& t) { unique_lock<shared_mutex> lc(_m); if (_que.empty())return false; t = move(_que.front()); _que.pop(); return true; } private: queue<T>_que; shared_mutex _m; }; class ThreadPool { private: class worker { public: worker(ThreadPool* _pool) : pool{ _pool } {} void operator ()() { while (!pool->_isShutDown) { { unique_lock<mutex> lock(pool->_m); pool->_cv.wait(lock, [this]() { return this->pool->_isShutDown || !this->pool->_que.empty(); }); } function<void()>func; bool flag = pool->_que.pop(func); if (flag) { func(); } } } private: ThreadPool* pool; }; public: ThreadPool(int n) : _threads(n), _isShutDown{ false } { for (auto& t : _threads)t = thread{ worker(this) }; } ThreadPool(const ThreadPool&) = delete; ThreadPool(ThreadPool&&) = delete; ThreadPool& operator=(const ThreadPool&) = delete; ThreadPool& operator=(ThreadPool&&) = delete; template <typename F, typename... Args> auto submit(F&& f, Args &&...args) -> std::future<decltype(f(args...))> { function<decltype(f(args...))()> func = [&f, args...]() {return f(args...); }; auto task_ptr = std::make_shared<std::packaged_task<decltype(f(args...))()>>(func); std::function<void()> warpper_func = [task_ptr]() { (*task_ptr)(); }; _que.push(warpper_func); _cv.notify_one(); return task_ptr->get_future(); } ~ThreadPool() { auto f = submit([]() {}); f.get(); _isShutDown = true; _cv.notify_all(); for (auto& t : _threads) { if (t.joinable()) t.join(); } } private: bool _isShutDown; SafeQueue<std::function<void()>> _que; vector<std::thread>_threads; mutex _m; condition_variable _cv; }; class A { public: int a = 0; void P() { cout << 111 << endl; } }; class T { public: T(int num) : mThreadPool(new ThreadPool(8)) { }; void Start() { A a; cout << "Start A的地址:" << &a << endl; Deal(a); } void Deal(A& a) { cout << "Deal A的地址:" << &a << endl; mThreadPool->submit(std::bind(&T::Read, this, std::ref(a))); } void Read(A& a) { cout << "Read A的地址:" << &a << endl; } private: std::unique_ptr<ThreadPool> mThreadPool; }; int main() { T t(8); t.Start(); return 0; }
原因分析
1. 栈对象生命周期过期导致悬垂引用
在T::Start()函数中,A a是栈上创建的局部对象。随后通过std::ref(a)将其引用绑定到std::bind生成的函数对象中,提交给线程池等待执行。但Start()函数执行完毕后,栈对象a会被立即销毁,内存被回收或标记为无效。
- Windows的内存管理机制不会立即覆盖已销毁栈对象的内存区域,工作线程执行
Read函数时可能还能读取到残留的内存内容,因此不会触发错误。 - Linux的内存系统会更及时地回收栈内存,工作线程访问已失效的引用时,直接触发段错误。
2. submit函数中lambda捕获的悬垂引用问题
在ThreadPool::submit函数中,lambda表达式[&f, args...]()捕获了参数f的引用:
function<decltype(f(args...))()> func = [&f, args...]() {return f(args...); };
f是submit的函数参数,当submit函数执行完成后,f的生命周期结束,此时func中保存的引用就变成了悬垂引用。工作线程执行func()时,会访问已销毁的f对象内存,这也是触发段错误的潜在原因,不同系统的内存回收策略差异导致表现不一致。
3. 线程池析构与任务执行的竞态条件
程序结束时,main函数中的T t对象销毁,会触发ThreadPool的析构函数。析构函数中先提交空任务并等待完成,再设置_isShutDown并通知所有线程。但Start()提交的任务可能还未执行,工作线程可能在_isShutDown设置后才尝试执行任务,此时任务依赖的对象(如A a)已经销毁,进一步加剧了内存访问错误的概率。
修复建议
- 将栈对象
A a改为动态分配(如std::shared_ptr<A>),延长其生命周期至任务执行完毕。 - 修改
submit函数中的lambda捕获方式,将&f改为按值捕获f,避免悬垂引用:[f, args...]()。 - 确保线程池中的任务执行完成后再销毁相关依赖对象,比如在
T的析构函数中等待所有提交的任务完成。
内容的提问来源于stack exchange,提问作者Sunjal
相关产品推荐
相关产品推荐

