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

线程池常见面试问答

好嘞 👍,我帮你把这些 线程池 + 并发编程八股文 整理成 问答对照表(Q & A),你面试时可以直接用。


🧾 线程池常见面试问答


一、基础语法 & STL

Q1:std::function<void()> 和函数指针的区别?

  • std::function 是通用的可调用包装器,可以存储 lambda、函数、仿函数、函数指针

  • 函数指针只能存一个函数地址,不支持捕获环境。

  • std::function 内部通过 类型擦除(type erasure) 实现,灵活但有一定性能开销(可能涉及动态分配)。


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

  • 避免拷贝,直接把任务对象所有权转移到局部变量 task

  • 如果不用 move,则会调用拷贝构造,多一次内存分配/复制。

  • 不能返回引用,因为 queue.pop() 后元素生命周期结束。


Q3:emplace_backpush_back 的区别?

  • push_back 需要先构造对象再拷贝/移动到容器。

  • emplace_back 在容器内部 原地构造,省一次拷贝/移动。

  • 这里 emplace_back([this]{...}) 直接在 vector 里构造线程对象。


Q4:Lambda 捕获 [this] 有什么风险?

  • [this] 捕获当前对象指针,如果对象先析构而线程还在运行,就会访问悬空指针 → 未定义行为

  • 避免方式:

    • 在析构函数里设置 stop_ = true;join() 所有线程。

    • 或者用 shared_from_this 配合智能指针,保证生命周期。


Q5:unique_locklock_guard 的区别?

  • lock_guard:轻量级,构造时加锁,析构时解锁,不可解锁/再锁。

  • unique_lock:更灵活,可以手动 unlock()/lock(),支持 condition_variable::wait()

  • 所以线程池里必须用 unique_lock,否则 wait() 无法自动释放锁。


二、并发编程原理

Q6:为什么 condition_variable::wait() 需要谓词?

  • 防止 虚假唤醒(spurious wakeup)

  • wait(lock, pred) 会反复检查 pred,直到返回 true。

  • 否则可能在任务队列仍为空时提前唤醒,导致线程取不到任务。


Q7:为什么 stop_ 不用 atomic<bool>

  • 因为所有对 stop_ 的读写都在 mutex 锁保护下,互斥锁已经保证内存可见性。

  • 如果去掉锁,仅依赖原子操作也能工作,但需要小心任务队列的并发访问。


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

  • 避免任务执行期间长时间持锁,阻塞其他线程取任务。

  • 正确做法:取任务时加锁,执行任务时释放锁


Q9:析构函数里为什么先 stop_ = truenotify_all()

  • 如果先通知,线程被唤醒时可能还看不到 stop_ 改变,导致重新进入等待,形成死锁。

  • 必须先修改标志,再通知,让线程看到正确的退出条件。


Q10:如果任务抛出异常会怎样?

  • 默认情况下,线程函数中未捕获的异常会调用 std::terminate(),整个程序崩溃。

  • 改进方法:在任务执行时用 try-catch 包裹,保证异常被吞掉或记录。


三、线程池设计扩展

Q11:如何让 submit 支持返回值?

  • std::packaged_task<T()> 包装任务,返回 std::future<T>

  • 提交时存放 packaged_task 到队列,执行时调用它,主线程可通过 future 拿结果。


Q12:线程池关闭时,任务会怎样?

  • 当前设计是 等所有已提交任务执行完再退出

  • 如果想要立即关闭并丢弃剩余任务,可以:

    • 在析构里清空队列。

    • 或在 submit() 里检测 stop_,拒绝新任务。


Q13:线程数怎么选择?

  • std::thread::hardware_concurrency():返回 CPU 核心数。

  • CPU 密集型任务:线程数 ≈ 核心数。

  • IO 密集型任务:线程数 > 核心数,考虑 2*N 或更多。


Q14:如何支持任务优先级?

  • std::queue 换成 std::priority_queue,根据任务优先级排序。

  • 也可以设计多个队列,高优先级任务先取。


Q15:线程池的任务队列属于什么模型?

  • 本质是 生产者-消费者模型

    • submit() = 生产者,往队列塞任务。

    • 工作线程 = 消费者,从队列取任务执行。

  • 如果不加锁,会出现数据竞争(多线程同时修改队列导致未定义行为)。


四、开放性 / 加分题

Q16:stop_volatile 行不行?

  • 不行。volatile 只保证编译器不优化读写,但不保证原子性和可见性。

  • 必须用 atomicmutex 才能保证线程安全。


Q17:什么是工作窃取(work stealing)线程池?

  • 普通线程池:所有线程从一个全局队列取任务。

  • 工作窃取:每个线程有自己的任务队列,如果自己队列空,就去“偷”别的线程的任务。

  • 优点:减少竞争,提高负载均衡。


Q18:线程池和协程的区别?

  • 线程池:真正的 OS 线程调度,切换开销大(上下文切换)。

  • 协程:用户态调度,切换只在用户空间进行,开销更小。

  • 协程适合大量 IO 密集型任务;线程池适合 CPU 密集型并行任务。


Q19:无锁队列能提升性能吗?

  • 可以,减少锁竞争。

  • 但需要处理 ABA 问题(两个线程同时修改链表节点)。

  • 解决方法:使用带版本号的 CAS 或 Hazard Pointers。


Q20:如果提交任务太多,队列会爆吗?

  • 是的,任务队列可能无限增长,耗尽内存。

  • 解决办法:

    • 设置队列最大长度,超出时阻塞/拒绝新任务。

    • 或者实现丢弃策略(丢弃最旧/最新任务)。


总结口诀:

  • 语法:function/move/lambda/unique_lock

  • 并发:虚假唤醒/锁外执行/内存可见性/异常安全

  • 扩展:future/packaged_task/优先级/关闭策略

  • 高阶:work stealing/协程/无锁队列


要不要我把这一套整理成 Markdown 表格格式,每个问题和答案一行,方便你直接打印/背诵?

http://www.xdnf.cn/news/1411597.html

相关文章:

  • 一次解决 Elasticsearch 两大难题: 掌握去重和深分页的最佳实践
  • Day19_【机器学习—线性回归 (1)】
  • PerfectSquares.java
  • c++程序员日常超实用工具(长期记录更新)
  • 疯狂星期四文案网第56天运营日记
  • 创意无界:云渲染如何让视觉创作触手可及
  • python如何下载svg图片
  • 【LeetCode - 每日1题】解数独
  • 虚幻引擎技术开放日!facecar分享3D HMI设计与UE开发经验
  • 基于单片机智能电子秤/称重计费
  • Idea启动错误-java.lang.OutOfMemoryError:内存不足错误。
  • DBeaverEE Mac 数据库管理工具
  • 决胜千里之外:服务器及硬件项目标书制作全流程与避坑指南
  • 《SVA断言系统学习之路》【02】并发断言
  • leetcode解题思路分析(一百六十六)1438 - 1444 题
  • 【机器学习基础】无监督学习算法的现代演进:从数据探索到智能系统的自主发现能力
  • 深入理解Nginx反向代理及其应用
  • 京东商品评论接口技术实现:从接口分析到数据挖掘全方案
  • 【Android】Notification 的基本使用
  • [线上问题排查]深度剖析:一条MySQL慢查询的全面优化实战
  • Cesium 入门教程(十四):鼠标键盘交互
  • 设置Ubuntu 22.04 LTS上的rsync同步服务
  • 提取动漫图像轮廓并拟合为样条曲线(MATLAB)
  • WEB漏洞挖掘篇(一) 基本概念、十大常見WEB漏洞
  • Python训练营打卡Day49-神经网络调参指南
  • 赵玉平《刘备谋略》读书笔记(上部)
  • 如何通过 AI IDE 集成开发工具快速生成简易留言板系统
  • 链表OJ做题报告
  • 批量修改用户密码的命令chpasswd
  • 使用组合子构建抽象语法树