为何此ThreadPool在析构时死锁?如何基于std::barrier修复?
C++20 std::barrier线程池死锁问题分析与修复
问题根源
死锁的核心原因是析构函数执行顺序错误,以及子线程收到停止请求时未正确处理std::barrier的计数:
- 原析构函数先唤醒子线程(设置
start并notify_all),再请求线程停止。这导致子线程被唤醒后,未检测到停止信号就进入任务处理流程,最终卡在sync_point.arrive_and_wait(),等待主线程到达屏障。 - 主线程此时正在等待子线程
join,不会再调用arrive_and_wait(),形成循环等待的死锁。 - 额外风险:析构时子线程执行任务逻辑时,
task指针可能已失效,触发未定义行为。
保留std::barrier的小幅修改方案
只需调整析构顺序并修正子线程的屏障处理逻辑,即可解决问题:
1. 调整析构函数的执行顺序
先请求线程停止,再唤醒等待的子线程,避免子线程进入无效的任务流程:
ThreadPool::~ThreadPool() { // 先请求所有线程停止 for (auto& t : threads) { t.request_stop(); } // 唤醒所有等待start的线程 start.test_and_set(); start.notify_all(); // 等待线程退出 for (auto& t : threads) { t.join(); } }
2. 修正子线程的屏障与停止逻辑
子线程被唤醒后立即检查停止请求,若已触发停止,通过arrive_and_drop()退出屏障阶段(避免其他线程等待);任务执行中也加入停止检查,确保及时响应:
void ThreadMain(std::stop_token stoken, ThreadPool* pool) { while (!stoken.stop_requested()) { pool->start.wait(false); // 被唤醒后优先检查停止请求 if (stoken.stop_requested()) { pool->sync_point.arrive_and_drop(); return; } int i{0}; while ((i = pool->idx.fetch_sub(1) - 1) > -1) { (*(pool->task))(pool->ctx, i); // 任务执行中也响应停止请求 if (stoken.stop_requested()) { break; } } // 根据停止状态选择屏障操作 if (!stoken.stop_requested()) { pool->sync_point.arrive_and_wait(); } else { pool->sync_point.arrive_and_drop(); return; } } }
3. 原代码其他可优化点(可选)
- 任务指针
task和上下文ctx可改用std::atomic指针,避免析构时的竞态访问; - 屏障的完成函数可保留原逻辑,确保
start标志正确清空:sync_point(num_threads, [&]() noexcept { this->start.clear(); })
验证说明
修改后重新编译运行:
g++ main.cpp -O0 -g --std=c++20
线程池析构时,子线程会正确响应停止请求,通过arrive_and_drop()减少屏障计数,不会卡在等待主线程的状态,主线程可正常完成join,死锁问题解决。
内容的提问来源于stack exchange,提问作者fabian
相关产品推荐
相关产品推荐

