现代并发数据结构

共 35 题
#

1. Wait-Free 与 Lock-Free 的区别中为什么真正的 Wait-Free 实现极其困难?实际工程中的取舍?

A Wait-Free 实现简单,性能开销小
B Lock-Free 保证每个线程不被饿死
C Wait-Free 保证每个线程在有限步数内完成,Lock-Free 只保证系统整体前进 ✓ 正确答案
D 两者都允许单个线程无限等待
#

2. 读-写锁与写优先中在缓存一致性场景如何选择?

A 读-写锁不允许读者并发读
B 读优先策略会让写者获得更高吞吐
C 缓存一致性场景不应考虑写者公平性
D 写优先策略保证写者不饥饿,避免长期被读者阻塞 ✓ 正确答案
#

3. 环形缓冲(ring buffer)的写满/读空判定(size 计数法)

A size 计数法无法区分 head==tail 时空与满的歧义
B size 计数法用独立计数器区分空与满,能存满整个容量 ✓ 正确答案
C size 计数法必须浪费一格空间
D size 计数法只适用于多生产者场景
#

4. RCU 与读写锁、无锁链表/队列的边界差异?

A RCU 读者必须加锁才能保证安全
B RCU 读者无锁且可并发读旧数据,写者延迟回收旧版本 ✓ 正确答案
C 读写锁读者无锁,性能最高
D 无锁结构与 RCU 一样需要 grace period 延迟回收
#

5. Seqlock(序列锁)的原理中适合读多写少的场景?写者饥饿问题如何缓解?

A 读者通过两次读序列号并比较来校验数据一致性,无需加锁 ✓ 正确答案
B Seqlock 适合写多读少的场景
C 读者需要加写锁才能获得一致性
D Seqlock 的读者会阻塞写者
#

6. MPMC(多生产者多消费者)队列的设计中 LMAX Disruptor 的 Ring Buffer 为什么比 LinkedBlockingQueue 快?

A 它比 LinkedBlockingQueue 慢,只是更简单
B 它仍使用一把全局锁保护所有操作
C 它依赖动态分配节点来降低成本
D 它预分配环形缓冲、用无锁序号推进并规避伪共享 ✓ 正确答案
#

7. 读多写少场景的 COW 与 seqlock 选择中为什么 COW 适合不可变引用(atomic shared_ptr),而 seqlock 适合标量读?

A COW 适合标量频繁读,seqlock 适合大对象引用
B COW 用原子指针发布不可变快照,适合读者拿引用的场景;seqlock 用序列号校验,适合标量读 ✓ 正确答案
C COW 读者必须加锁
D seqlock 每次读都要复制整个大对象
#

8. Flat Combining 技术中如何将多线程竞争转化为单线程顺序执行以提升吞吐?

A 它让单一执行者在持锁时批量处理提交的操作,减少锁竞争与缓存乒乓 ✓ 正确答案
B 每个线程都直接操作数据结构,并行执行
C 它只适用于无锁实现,不适用于锁
D 它通过增加锁竞争来提升吞吐
#

9. Copy-on-Write 容器的适用边界中读多写少场景的收益与写放大代价?

A COW 写操作不复制任何数据
B 它适合写非常频繁的场景,写放大可忽略
C COW 读者必须加锁才能保证一致
D 它适合读多写少、容器适中的场景,读者无锁并获得一致快照,但每次写要全量复制 ✓ 正确答案
#

10. CAS 强竞争下的活锁问题中无锁队列为何可能长时间无法前进,backoff 与批次化如何缓解?

A backoff 通过增加 CAS 频率来解决问题
B 活锁会导致线程全部阻塞死亡
C 活锁下线程都在运行但互相 CAS 失败重试,系统无法取得进展 ✓ 正确答案
D 批次化会增加 CAS 次数,加剧竞争
#

11. 为何不能用"头==尾"同时表示空与满,需要额外标记

A 空与满时 head 与 tail 位置重合,需用 size 计数或标志位额外区分 ✓ 正确答案
B 空时 head!=tail,满时 head==tail,可直接区分
C 只要 head 与 tail 是整数就不会歧义
D 头==尾只能表示满,不能表示空
#

12. Linux 内核中 RCU 在路由表/文件系统的典型运用

A RCU 只用于文件系统,不用于路由表
B 内核路由查找必须加锁保护
C 路由表与 dentry 缓存读路径无锁,写者用 RCU 替换并延迟回收旧对象 ✓ 正确答案
D 写者更新后立即回收旧对象
#

13. RCU 与垃圾回收在"延迟释放"理念上的相似

A RCU 依赖可达性分析自动回收
B RCU 立即回收旧对象,GC 才延迟
C GC 用过 grace period 判断安全
D 两者都延迟释放仍被并发引用的对象,待确认无人引用后再回收 ✓ 正确答案
#

14. RCU 在 Linux 内核、读多写少场景的典型应用?

A RCU 适合写非常频繁、读者必须读最新的场景
B 内核路由表、dentry 缓存等读多写少结构用 RCU 实现读者无锁 ✓ 正确答案
C RCU 读者需要加锁才能读
D RCU 只适用于用户态,不适用于内核
#

15. RCU 使用有哪些常见误区(写者开销、内存回收时机)?

A 写者需等待 grace period 才能回收旧对象,且 `synchronize_rcu` 有真实写延迟开销 ✓ 正确答案
B RCU 读者无锁,因此写路径也完全免费
C 写完发布指针后可以立即 free 旧对象
D 读者可以在临界区内睡眠以加速
#

16. RCU 的核心概念中读侧临界区、宽限期(grace period)与回收?

A 宽限期结束后读者仍可能引用旧指针
B 写者发布新指针后需等宽限期结束(所有读者退出临界区)才能回收旧对象 ✓ 正确答案
C 读者在临界区内可以任意阻塞
D 回收与宽限期无关,写者可立即 free
#

17. RCU 适用于"读多写少"且写者能容忍延迟回收的场景

A RCU 读者必须读到最新值
B RCU 适合写非常频繁、读者必须读最新的场景
C RCU 适合读多写少且写者能容忍延迟回收的场景 ✓ 正确答案
D RCU 写者可以立即释放旧对象
#

18. Lock-Free Queue(Michael-Scott)的 CAS 循环设计中 ABA 问题如何用 Hazard Pointer 或 Epoch-Based Reclamation 解决?

A Hazard Pointer 无法解决 ABA 问题
B ABA 问题是因为 CAS 没有版本号导致的,与内存回收无关
C EBR 按单个指针粒度保护
D Hazard Pointer 保护被并发引用的指针推迟回收,EBR 按时代批量回收对象 ✓ 正确答案
#

19. RCU(Read-Copy-Update)的核心思想中读路径无锁?Grace Period 如何确定安全回收时机?Linux 内核中的应用?

A 回收是即时的,无需等待
B 写者在原地修改共享数据,读者加锁
C 读者必须加锁才能读到一致数据
D 写者复制-修改-发布新指针,读者无锁读,grace period 后回收旧对象 ✓ 正确答案
#

20. Concurrent Skip List 的设计中为什么 Redis 的 ZSET 选择跳表而非红黑树?并发跳表的锁粒度(per-node vs per-level)?

A Redis 的 ZSET 选跳表是因为实现简单、支持范围查询且对并发友好,而非红黑树 ✓ 正确答案
B 红黑树的旋转操作对并发更友好
C 并发跳表只能使用 per-level 锁,无法细粒度
D 跳表不支持范围查询
#

21. Treiber 无锁栈与 Michael-Scott 队列的区别中栈的 CAS 循环比队列简单,队列为何需要 dummy 节点?

A 队列的 dummy 节点用于存储数据
B 队列只有一个指针,比栈简单
C 栈需要 dummy 节点
D 栈只有一个栈顶指针、CAS 循环简单;队列双指针且需 dummy 节点消除空队列歧义 ✓ 正确答案
#

22. Hazard Pointer 与 Epoch-Based Reclamation(EBR)的对比中各自的内存开销和适用场景?

A Hazard Pointer 开销比 EBR 小
B EBR 按单个指针保护,开销最大
C Hazard Pointer 按指针精确保护、延迟低但每线程槽位开销大;EBR 按时代批量回收、开销小但可能延迟回收 ✓ 正确答案
D 两者都立刻释放对象
#

23. Concurrent Hash Map 的设计演进中 Java ConcurrentHashMap(JDK 7 分段锁 → JDK 8 CAS+synchronized)vs C++ TBB vs Rust DashMap?

A JDK 8 用桶级别 CAS+synchronized 替代 JDK 7 的分段锁,并发度更高 ✓ 正确答案
B JDK 7 的并发度比 JDK 8 更高
C 所有实现都用单一全局锁
D TBB 的锁粒度比 Java 8 更细
#

24. Disruptor 环形缓冲中单生产者/消费者无锁模型与伪共享(false sharing)规避?

A SPSC 下用相邻序号无锁推进,并用缓存行填充隔离各线程热变量避免伪共享 ✓ 正确答案
B SPSC 下仍需 CAS 保证正确
C 伪共享不影响性能,无需规避
D 缓存行填充会提高延迟
#

25. ConcurrentHashMap 在 Java 8 中 CAS+synchronized 的演进中从分段锁到桶级别锁,sizeCtl 控制扩容,红黑树退化优化

A sizeCtl 只记录元素个数
B 它仍使用 JDK 7 的分段锁
C 它用桶级别 CAS+synchronized 替代分段锁,用 sizeCtl 协调扩容,链表超阈值转红黑树 ✓ 正确答案
D 链表长度无论多大都不转红黑树
#

26. MPMC 无锁队列为何需要 CAS 与可能的 ABA 问题

A ABA 问题与内存回收无关
B MPMC 无需 CAS,靠锁即可
C MPMC 需靠 CAS 原子推进共享指针,ABA 问题源于内存被释放重用导致地址回绕,可用版本号或延迟回收解决 ✓ 正确答案
D 单生产者比多生产者更易触发 ABA
#

27. 单生产者单消费者(SPSC)无锁队列如何靠内存屏障实现

A SPSC 队列必须用锁保护
B SPSC 仍需要 CAS 才能保证正确
C 内存屏障只能用于多生产者场景
D SPSC 无竞争,用 release 发布、acquire 读取的屏障保证数据可见性即可,无需锁或 CAS ✓ 正确答案
#

28. 无锁队列在高频交易/日志管道中的真实收益与调试难度

A 无锁队列只能用于低频场景
B 无锁队列比锁更快且更容易调试
C 无锁队列不存在 ABA 问题
D 它提供低延迟、高吞吐、无阻塞,但并发 bug 难复现、内存回收与内存序调试难度大 ✓ 正确答案
#

29. CAS 的 ABA 问题如何用版本号/双字 CAS 解决

A 双字 CAS 无法原子更新两个字段
B 版本号法只能解决死锁,不能解决 ABA
C 版本号法或双字 CAS 在比较中加入随修改递增的版本号,使地址回绕不再被误判为匹配 ✓ 正确答案
D ABA 问题无需解决,不影响正确性
#

30. Disruptor 框架如何用无锁 ring + 序号屏障实现高吞吐

A Disruptor 依赖锁来协调生产者与消费者
B 预分配 ring 复用槽位,序号屏障用序号协调生产者与消费者的读写边界,实现无锁高吞吐 ✓ 正确答案
C 序号屏障是用于分配内存的
D Disruptor 不预分配,靠动态分配节点
#

31. 内存序(acquire/release)在无锁结构中的关键作用

A 无锁结构不需要内存序,靠 CAS 即可
B release 发布数据、acquire 读取数据,二者配对形成发布-获取同步,保证数据可见性 ✓ 正确答案
C acquire 只影响写,release 只影响读
D 宽松内存序能保证数据可见性
#

32. 无锁读是否真的更快,内存屏障与缓存一致性的隐性成本

A 无锁读在任何场景都一定更快
B 无锁读省去锁等待,但内存屏障与缓存一致性(MESI 失效)是隐性成本,低竞争/读多写少时更划算 ✓ 正确答案
C 无锁结构没有缓存一致性成本
D 内存屏障是无锁结构的免费功能
#

33. COW(写时复制)页表/数据结构与 RCU 思想的联系

A 两者都通过复制旧数据、修改后原子发布、读者共享一致快照并延迟处理旧版本 ✓ 正确答案
B COW 与 RCU 读者都需要加锁
C COW 写者不复制数据
D RCU 读者读到的版本一定是新的
#

34. RCU(Read-Copy-Update)如何让读者完全无锁

A 写者复制-修改-原子发布新指针,读者只读当前指针并声明临界区,写者延迟回收旧版本 ✓ 正确答案
B 读者需要加锁才能读取数据
C 写者在原地修改数据,读者等待
D 读者必须用 CAS 读取指针
#

35. seqlock 如何用序列号让读者检测写者并发从而避免锁

A 读者必须加锁才能读取一致数据
B 读者读两次序列号(偶数)并比较,检测到写者并发则重试,从而无需加锁 ✓ 正确答案
C 序列号是奇数表示写完成
D 读者检测到写者后会阻塞等待