如何让ThreadPool等待所有任务完成?线程池阻塞与线程生命周期问题
问题描述
当线程池中的任务完成后,如果不调用shutdown()方法,整个进程会被阻塞。想了解在未调用thread.join()的情况下,如何保证主线程不会在工作线程之前被销毁?此前得到的结论是线程对象持有线程池的引用。
ThreadPool 实现代码
#pragma once #include <functional> #include <future> #include <mutex> #include <queue> #include <thread> #include <utility> #include <vector> #include "SafeQueue.h" class ThreadPool { private: class ThreadWorker { private: int m_id; ThreadPool * m_pool; public: ThreadWorker(ThreadPool * pool, const int id) : m_pool(pool), m_id(id) { } void operator()() { std::function<void()> func; bool dequeued; while (!m_pool->m_shutdown) { { std::unique_lock<std::mutex> lock(m_pool->m_conditional_mutex); if (m_pool->m_queue.empty()) { m_pool->m_conditional_lock.wait(lock); } dequeued = m_pool->m_queue.dequeue(func); } if (dequeued) { func(); } } } }; bool m_shutdown; SafeQueue<std::function<void()>> m_queue; std::vector<std::thread> m_threads; std::mutex m_conditional_mutex; std::condition_variable m_conditional_lock; public: ThreadPool(const int n_threads) : m_threads(std::vector<std::thread>(n_threads)), m_shutdown(false) { } ThreadPool(const ThreadPool &) = delete; ThreadPool(ThreadPool &&) = delete; ThreadPool & operator=(const ThreadPool &) = delete; ThreadPool & operator=(ThreadPool &&) = delete; // Inits thread pool void init() { for (int i = 0; i < m_threads.size(); ++i) { m_threads[i] = std::thread(ThreadWorker(this, i)); } } // Waits until threads finish their current task and shutdowns the pool void shutdown() { m_shutdown = true; m_conditional_lock.notify_all(); for (int i = 0; i < m_threads.size(); ++i) { if(m_threads[i].joinable()) { m_threads[i].join(); } } } // Submit a function to be executed asynchronously by the pool template<typename F, typename...Args> auto submit(F&& f, Args&&... args) -> std::future<decltype(f(args...))> { // Create a function with bounded parameters ready to execute std::function<decltype(f(args...))()> func = std::bind(std::forward<F>(f), std::forward<Args>(args)...); // Encapsulate it into a shared ptr in order to be able to copy construct / assign auto task_ptr = std::make_shared<std::packaged_task<decltype(f(args...))()>>(func); // Wrap packaged task into void function std::function<void()> wrapper_func = [task_ptr]() { (*task_ptr)(); }; // Enqueue generic wrapper function m_queue.enqueue(wrapper_func); // Wake up one thread if its waiting m_conditional_lock.notify_one(); // Return future from promise return task_ptr->get_future(); } };
测试代码
int add(const int x, const int y) { return x + y; } int main() { ThreadPool pool(2); pool.init(); auto ret = pool.submit(add, 1, 3); auto result = ret.get(); std::cout << result << std::endl; }
问题解答
进程阻塞的原因
当前线程池的工作线程在任务完成后,会进入m_conditional_lock.wait(lock)的等待状态——因为m_shutdown默认是false,工作线程不会退出循环,会一直阻塞在条件变量上。当主线程执行到main函数末尾时,局部对象pool会被析构,而pool中的std::thread对象仍处于可 joinable 状态(未调用join()或detach())。根据C++标准,这种情况下程序会调用std::terminate()终止进程,这就是你观察到的“进程阻塞”(实际是触发了强制终止逻辑)。
关于线程对象与线程池的引用关系
之前的结论存在偏差:std::thread对象本身并没有持有线程池的引用,而是工作线程的执行函数(ThreadWorker::operator())通过指针m_pool持有线程池的地址。但这和主线程是否被销毁没有直接关联——主线程的销毁(main函数结束)会触发局部对象的析构,核心矛盾在于std::thread对象的析构规则。
未调用join()时保证主线程安全的方案
- 调用
std::thread::detach():将工作线程与线程对象分离,分离后的工作线程会成为后台线程,当主线程结束时,后台线程会被操作系统强制终止。但这种方式不适合线程池场景——分离后无法再管理工作线程,也无法安全地关闭线程池,会导致资源泄漏风险。 - 修改线程池析构函数自动调用
shutdown():这是更合理的方案,在ThreadPool的析构函数中自动触发shutdown(),确保工作线程被正确等待并退出,避免进程被强制终止。添加如下代码到ThreadPool的public区域:
~ThreadPool() { if (!m_shutdown) { shutdown(); } }
这样即使忘记手动调用shutdown(),线程池析构时也会自动完成工作线程的回收,保证进程正常退出。
总结
线程池场景下,正确的做法始终是在主线程退出前调用shutdown()(或通过析构函数自动触发),确保工作线程完成任务后安全退出。依赖detach()的方案无法保证线程池资源的安全释放,不推荐使用。
内容的提问来源于stack exchange,提问作者zheng ruan

