当前位置: 首页 > news >正文

线程池八股文


🧾 线程池八股文


一、基础语法 & STL

Q1:std::function<void()> task; 在这里是什么意思?

  • workers_ 的线程循环里,定义了一个 task 变量,它是一个 通用可调用对象包装器

  • 这里统一把所有提交的任务(lambda、函数等)存放到 tasks_ 队列中,工作线程取出来放到 task 里,再调用 task() 执行。


Q2:为什么要 std::move(tasks_.front())

  • 因为 tasks_ 是一个 std::queue<std::function<void()>>

  • tasks_.front() 返回的是引用,如果直接赋值会触发一次拷贝构造;

  • std::move 可以把任务对象的所有权转移到局部变量 task,避免额外拷贝,提高效率。

  • 注意:不能返回引用,因为 tasks_.pop() 后,front() 指向的元素生命周期就结束了。


Q3:workers_.emplace_back([this]{ ... }) 为什么用 emplace_back

  • workers_ 是一个 std::vector<std::thread>

  • emplace_back 可以在 vector 内部直接构造一个 std::thread 对象,避免一次多余的拷贝/移动。

  • 在这里,lambda 捕获了 this,所以每个线程都能访问 tasks_queue_mutex_condition_


Q4:为什么在 condition_.wait() 里必须用 unique_lock 而不是 lock_guard

  • condition_.wait() 内部会在阻塞期间 释放 queue_mutex_,唤醒后再重新加锁。

  • lock_guard 不能解锁再上锁,所以必须用 std::unique_lock<std::mutex>


二、并发编程原理

Q5:为什么 condition_.wait(lock, [this]{ return !tasks_.empty() || stop_; }); 要有谓词?

  • 因为可能发生 虚假唤醒 或竞争唤醒。

  • 如果不用谓词,线程可能在 tasks_ 仍然为空时就被唤醒,然后执行 task = tasks_.front() → 直接越界崩溃。

  • 谓词 !tasks_.empty() || stop_ 确保只有在队列里有任务,或者收到停止信号时,线程才会继续。


Q6:为什么在锁外执行 task()

task = std::move(tasks_.front());
tasks_.pop();
} // 这里锁释放
task(); // 在锁外执行
  • 因为 task() 可能是一个耗时任务,如果在持有 queue_mutex_ 时执行,就会阻塞其他线程从 tasks_ 取任务。

  • 正确做法:取出任务后立即释放 queue_mutex_,让其他线程也能并发消费队列。


Q7:析构函数里为什么要先 stop_ = truecondition_.notify_all()

  • 析构函数里加锁修改 stop_

    {std::unique_lock<std::mutex> lock(queue_mutex_);stop_ = true;
    }
    condition_.notify_all();
    
  • 必须先改 stop_,再通知所有等待在 condition_ 上的线程。

  • 否则线程被唤醒时看不到最新的 stop_ 值,会继续 wait,导致死锁。


Q8:为什么 stop_ 不需要是 std::atomic<bool>

  • 因为所有对 stop_ 的访问都在 queue_mutex_ 锁保护下。

  • 互斥锁已经保证了内存可见性和顺序一致性,所以没必要额外用 atomic


三、线程与任务模型

Q9:线程和 CPU 的关系是什么?

  • 在代码里,workers_ 存的是一组 std::thread 对象,每个线程需要被操作系统调度到 CPU 核心上才能运行。

  • 如果线程数 ≤ CPU 核心数 → 真并行;

  • 如果线程数 > CPU 核心数 → 操作系统用时间片轮转,让 workers_ 中的线程轮流在 CPU 上跑。


Q10:CPU 密集 vs IO 密集,在 ThreadPool 参数选择上有什么区别?

  • CPU 密集型任务:比如 task() 里做矩阵运算,CPU 总是满载,线程池大小 ≈ CPU 核心数。

  • IO 密集型任务:比如 task() 里做网络请求,CPU 大部分时间在等 IO → 可以设置 workers_ 数量为 CPU 核心数的 2~4 倍,提高 CPU 利用率。


四、扩展设计

Q11:现在的 submit(std::function<void()> task) 只能提交 void() 任务,如何支持返回值?

  • 可以改为用 std::packaged_task<T()> 封装任务,把 future 返回给调用者。

  • 工作线程依旧在 workers_ 中执行 (*task)();,结果会写入 future 的共享状态。


Q12:如果 task() 抛异常会怎样?

  • 如果 task() 里抛出未捕获的异常,该工作线程会调用 std::terminate(),导致整个程序崩溃。

  • 解决方法:在 task(); 外层包一层 try-catch,把异常捕获并记录下来,保证线程池不会因为一个任务挂掉。


五、面试总结套路(带参数名)

当面试官问你“解释一下这个线程池”时,你可以这样回答:

  1. 整体思路

    • workers_ 里预先创建了 N 个线程,它们在循环里等待任务。

    • 任务被压入 tasks_ 队列,condition_ 唤醒等待的线程。

    • 线程取任务时持有 queue_mutex_,保证对队列的访问安全。

    • 析构时设置 stop_ = true,通知所有线程退出并 join。

  2. 关键细节

    • std::function<void()> 存放任意任务。

    • std::unique_lock + condition_.wait() 防止虚假唤醒。

    • 任务取出后在锁外执行,避免阻塞其他线程。

    • 析构时先改 stop_notify_all(),避免死锁。

  3. 扩展点

    • 返回值支持 → std::packaged_task + std::future

    • 优先级任务 → std::priority_queue

    • 过载保护 → 限制 tasks_ 大小,丢弃或阻塞提交。

http://www.dtcms.com/a/359714.html

相关文章:

  • 语义分析:从读懂到理解的深度跨越
  • Python基础:函数
  • Visual Studio Code中launch.json的解析笔记
  • 【Canvas与旗帜】哥伦比亚旗圆饼
  • 【芯片测试篇】:LIN总线
  • 人工智能-python-深度学习-
  • 自制扫地机器人(一)20 元级机械自动避障扫地机器人——东方仙盟
  • 计算机网络---http(超文本传输协议)
  • 【开题答辩全过程】以 留守儿童志愿者服务系统为例,包含答辩的问题和答案
  • 从企业和业务视角来拒绝中台
  • 什么是 WAF?全面解读 Web 应用防火墙的原理与应用
  • Vue3 响应式基础
  • TFS-2002《Fuzzy Clustering With Viewpoints》
  • 在SAP系统中,如何查询已经被打上了删除标记的生产订单?
  • AI 赋能 Java 开发效率:全流程痛点解决与实践案例(一)
  • 云网络(参考自腾讯云计算工程师认证)
  • 【开题答辩全过程】以 基于微信小程序的教学辅助系统 为例,包含答辩的问题和答案
  • ES集群部署-EFK架构实战
  • 【Python基础】2. 常量与变量
  • c++ Effective c++ 条款5
  • Ubuntu24.04(Jazzy)从零开始实现环境配置和Gmapping建图
  • 北京博乐科技有限公司2025届程序技术类笔试题
  • 京东获取商品评论指南,实时关注用户反馈
  • 【秋招笔试】2025.08.30科大讯飞秋招笔试题
  • PetaLinux的JTAG启动
  • Linux/UNIX系统编程手册笔记:用户和组、进程凭证、时间以及系统限制和选项
  • 创维LB2004_安装软件教程
  • vscode克隆远程代码步骤
  • MSVC---编译器工具链
  • 基于GA遗传优化的双向LSTM融合多头注意力(BiLSTM-MATT)时间序列预测算法matlab仿真