整本书测试与巩固_《C++并发编程实战》笔记
为了方便自己以后巩固与复习, 故出20道选择和10道编程设计题目, 后面会给出参考答案
1. 多选题目(20道)
-
关于std::thread的构造和析构,哪些说法正确?
A. std::thread对象析构前必须调用join()或detach()
B. 对已调用detach()的线程,无法再调用join()
C. 对一个未调用join()的std::thread对象析构会引发std::terminate
D. join()后的线程可以通过std::move转移给其他std::thread对象 -
线程参数传递的陷阱可能包括哪些?
A. 传递局部变量的指针,但线程运行时变量已被销毁
B. 使用std::ref传递非const引用
C. 传参时发生隐式类型转换(如const char*转std::string)
D. 传递std::unique_ptr时未使用std::move -
关于线程所有权转移,正确的是?
A. std::thread对象只能通过std::move转移所有权
B. std::thread t1(f); std::thread t2 = t1;是合法操作
C. std::thread对象可作为函数返回值
D. std::thread对象存入std::vector需要显式调用std::move -
关于join()和detach(),错误的是?
A. join()会阻塞当前线程直至目标线程结束
B. detach()后的线程生命周期与std::thread对象无关
C. 对已join()的线程再次调用join()会导致编译错误
D. detach()的线程资源由C++运行时自动回收 -
如何正确使用RAII管理线程?
A. thread_guard在析构时调用join()
B. scoped_thread的构造函数必须检查线程是否可join
C. RAII对象在异常发生时自动释放线程资源
D. thread_guard禁止拷贝构造和赋值操作 -
下面代码可能的问题是什么?
void f(int& x) { x++; } int main() { int a = 0; std::thread t(f, a); t.join(); }
A. 参数a未使用std::ref传递
B. x++未加锁,导致数据竞争
C. 线程t的参数会拷贝到新线程栈
D. a的修改对主线程不可见 -
关于硬件并发的正确描述是?
A. std::thread::hardware_concurrency()返回的是物理CPU核心数
B. 硬件并发数一定等于可并行执行的最大线程数
C. 任务切换可以实现与硬件并发相同的性能
D. 多核系统中,硬件并发数可能大于1 -
以下哪些操作会抛出std::system_error?
A. 对未关联线程的std::thread对象调用join()
B. 对已detach()的std::thread对象调用detach()
C. 对joinable()为false的线程调用join()
D. 构造std::thread时参数类型不匹配 -
关于std::async,正确的是?
A. 默认策略下可能在新线程或当前线程执行任务
B. std::launch::deferred表示延迟到get()调用时执行
C. std::async的返回值可以忽略
D. 必须通过std::future获取结果 -
以下场景可能引发数据竞争的是?
A. 两个线程同时读取同一非原子变量
B. 一个线程写,另一个线程读未同步的共享变量
C. 使用std::mutex保护所有共享变量访问
D. 原子变量的load()和store()无内存序约束 -
关于互斥量,错误的是?
A. std::lock_guard在作用域结束时自动释放锁
B. std::unique_lock支持手动加锁和解锁
C. 递归互斥量允许同一线程重复加锁
D. std::mutex的lock()和unlock()必须配对调用 -
条件变量的正确用法是?
A. wait()必须在循环中检查条件
B. notify_one()会唤醒所有等待线程
C. std::condition_variable必须与std::mutex配合使用
D. wait()会自动释放锁并阻塞线程 -
关于原子操作,正确的描述是?
A. std::atomic::load()保证顺序一致性
B. memory_order_relaxed不提供同步保证
C. fetch_add()是原子的读-修改-写操作
D. 原子操作能完全取代互斥量的使用 -
设计无锁数据结构的关键挑战是?
A. 正确处理ABA问题
B. 确保所有操作均为原子
C. 避免死锁和优先级反转
D. 依赖硬件支持的原子指令 -
关于内存模型,错误的是?
A. C++内存模型定义了多线程下的操作可见性
B. std::atomic默认使用memory_order_seq_cst
C. 编译器可能对非原子操作进行指令重排
D. memory_order_consume适用于依赖数据排序 -
线程池的优点是?
A. 减少线程创建销毁的开销
B. 自动负载均衡
C. 避免线程数量超过硬件并发数
D. 支持优先级调度 -
以下哪些是多线程调试的常用工具?
A. Valgrind Helgrind
B. ThreadSanitizer
C. GDB的info threads命令
D. 基于日志的追踪 -
哪些情况下推荐使用并发?
A. 需要实时响应大量用户输入
B. 计算密集型任务可利用多核CPU
C. 单个任务的执行时间极短
D. IO操作频繁导致等待时间过长 -
以下说法错误的是?
A. 线程切换的开销通常小于进程切换
B. 并发一定比串行的性能更好
C. 数据并行适用于SIMD架构
D. 无锁数据结构一定比基于锁的高效 -
关于C++17并行算法,正确的是?
A. 使用std::execution::par指定并行策略
B. 所有标准算法均有并行版本
C. 并行算法要求输入范围支持随机访问
D. 可以自动避免数据竞争
2. 设计题目(10道)
-
多生产者多消费者线程安全环形队列
要求:
实现基于无锁设计的环形缓冲区,支持多个生产者和消费者同时操作。使用CAS保证操作的原子性,处理ABA问题。缓冲区满时生产者阻塞,空时消费者阻塞,使用C++11原子变量和条件变量。 -
内存屏障控制的多核计数器
要求:
设计高性能原子计数器,支持fetch_add操作,要求严格顺序一致性。使用memory_order_seq_cst,对比改为memory_order_relaxed后的正确性差异。验证不同内存序下多线程操作的可见性。 -
Promise/Future实现异步流水线
要求:
构建异步处理流水线:数据读取→预处理→加密→写入文件。每阶段用独立线程处理,阶段间用promise/future传递结果。处理异常传播,确保加密失败时整个流水线优雅终止。 -
可中断线程池实现
要求:
实现可响应外部中断的线程池,支持动态提交任务和立即取消未执行任务。使用std::jthread(或自定义中断点)停止运行中的任务。要求管理未完成任务队列的线程安全清理。 -
CAS无锁栈的ABA问题解决方案
要求:
实现无锁栈结构,解决ABA问题。采用"带标记指针"或双重CAS策略,验证插入/删除操作的线程安全性。测试高并发场景下栈的正确性,比较与有锁版本的性能差异。 -
基于条件变量的工作窃取调度器
要求:
设计工作窃取(Work-Stealing)调度器:每个Worker线程维护双端队列,空闲线程窃取其他队列任务。使用std::mutex和条件变量同步任务获取,避免死锁,实现负载均衡。 -
并行快速排序优化
要求:
使用C++17并行算法执行策略实现快速排序。对比std::execution::par与手动线程池实现的性能差异。处理递归划分时的任务调度,确保小数组切换为串行排序。 -
细粒度锁的哈希表设计
要求:
构建线程安全哈希表,每个桶独立加锁,支持高并发插入/查找/删除。实现动态扩缩容机制,确保rehash时不阻塞全表。测试不同锁粒度(全局锁 vs 分段锁)下的吞吐量。 -
检测并修复死锁场景
要求:
编写一个必然发生死锁的代码片段(例如:多个线程按不同顺序请求互斥锁)。使用分层锁策略或std::lock/std::scoped_lock修复死锁。集成Valgrind或TSAN工具验证修复效果。 -
原子操作实现无锁内存池
要求:
实现基于原子操作的无锁内存池,支持多线程高效分配/释放固定大小内存块。使用链表管理空闲块,通过CAS更新头指针。验证内存分配的正确性,处理高并发下的性能瓶颈。
参考答案:
// 待完成