h5 服装网站模板潍坊百度快速排名优化
好嘞 👍,我帮你把这些 线程池 + 并发编程八股文 整理成 问答对照表(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_back 和 push_back 的区别?
- push_back需要先构造对象再拷贝/移动到容器。
- emplace_back在容器内部 原地构造,省一次拷贝/移动。
- 这里 - emplace_back([this]{...})直接在- vector里构造线程对象。
Q4:Lambda 捕获 [this] 有什么风险?
- [this]捕获当前对象指针,如果对象先析构而线程还在运行,就会访问悬空指针 → 未定义行为。
- 避免方式: - 在析构函数里设置 - stop_ = true;并- join()所有线程。
- 或者用 - shared_from_this配合智能指针,保证生命周期。
 
Q5:unique_lock 和 lock_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_ = true 再 notify_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只保证编译器不优化读写,但不保证原子性和可见性。
- 必须用 - atomic或- mutex才能保证线程安全。
Q17:什么是工作窃取(work stealing)线程池?
- 普通线程池:所有线程从一个全局队列取任务。 
- 工作窃取:每个线程有自己的任务队列,如果自己队列空,就去“偷”别的线程的任务。 
- 优点:减少竞争,提高负载均衡。 
Q18:线程池和协程的区别?
- 线程池:真正的 OS 线程调度,切换开销大(上下文切换)。 
- 协程:用户态调度,切换只在用户空间进行,开销更小。 
- 协程适合大量 IO 密集型任务;线程池适合 CPU 密集型并行任务。 
Q19:无锁队列能提升性能吗?
- 可以,减少锁竞争。 
- 但需要处理 ABA 问题(两个线程同时修改链表节点)。 
- 解决方法:使用带版本号的 CAS 或 Hazard Pointers。 
Q20:如果提交任务太多,队列会爆吗?
- 是的,任务队列可能无限增长,耗尽内存。 
- 解决办法: - 设置队列最大长度,超出时阻塞/拒绝新任务。 
- 或者实现丢弃策略(丢弃最旧/最新任务)。 
 
✅ 总结口诀:
- 语法: - function/move/lambda/unique_lock
- 并发: - 虚假唤醒/锁外执行/内存可见性/异常安全
- 扩展: - future/packaged_task/优先级/关闭策略
- 高阶: - work stealing/协程/无锁队列
要不要我把这一套整理成 Markdown 表格格式,每个问题和答案一行,方便你直接打印/背诵?
