14.组成原理思考题答案
# 14.组成原理思考题答案
覆盖 12 篇全部思考题。基础题给结论,进阶题给推导,动手题给思路框架。
# 第 01 篇·计算机组成结构原理
# 基础思考题
Q1:五大部件对号入座
| 硬件 | 部件归属 |
|---|---|
| 机械硬盘 / SSD | 存储器(外存/辅存) |
| RAM | 存储器(内存/主存) |
| CPU 的 ALU | 运算器 |
| CPU 的程序计数器 PC | 控制器 |
| 键盘 | 输入设备 |
| 打印机 | 输出设备 |
| 触摸屏 | 既是输入又是输出(双向设备) |
| 网卡 | 既是输入又是输出(发送=输出,接收=输入) |
| GPU | 独立部件,核心是运算器(大量并行 ALU)+ 显存 |
Q2:为什么程序必须先加载到内存才能运行?
CPU 通过地址总线访问内存,指令和数据都存放在内存中,CPU 的 PC(程序计数器)只指向内存地址。如果允许 CPU 直接读硬盘:① 硬盘速度 ~10ms,内存 ~100ns,CPU 要等 10 万倍的时间——CPU 99.999% 时间在空转;② 硬盘没有统一的地址空间(涉及文件系统、LBA 扇区号),CPU 无法直接用"地址"寻址。
内存充当了速度缓冲层和统一地址空间。操作系统通过虚拟内存让硬盘上的程序"看起来"在内存里,需要时再换入。
Q3:指令周期四个阶段
a = b + c 的五大部件数据流:
取指(Fetch):PC → MAR → 内存 → MDR → IR(控制器主导)
解码(Decode):IR 操作码 → CU 解析(控制器)
执行(Execute):CU 控制 ALU,b → ACC,c → X,ACC + X = 结果(运算器)
写回(Write Back):结果 → MDR → MAR → 内存地址 a(运算器→控制器→存储器)
PC+1 → 下一条指令
参与者:控制器(全程指挥,PC/MAR/MDR/IR/CU)、存储器(存指令和操作数)、运算器(ALU 做加法)。
Q4:冯·诺依曼 vs 哈佛架构
| 冯·诺依曼 | 哈佛 | |
|---|---|---|
| 存储 | 指令+数据共用存储器 | 指令/数据分开存储 |
| 总线 | 一条总线,串行访问 | 两条总线,可并行 |
| 瓶颈 | 取指和取数冲突 | 硬件更复杂 |
现代 CPU L1 用哈佛(I-Cache + D-Cache 并行访问,因为取指令和读数据是流水线里不同阶段经常同时发生的操作),内存层用冯·诺依曼(统一 DDR 降低成本,且 L2/L3 统一缓存桥接了两套 L1)。
# 进阶思考题
Q1:Amdahl 定律计算
P=0.8,N=4→32:
- 4 核:
S = 1/((1-0.8)+0.8/4) = 1/(0.2+0.2) = 2.5x - 32 核:
S = 1/((1-0.8)+0.8/32) = 1/(0.2+0.025) ≈ 4.44x
如果把 P 从 80% 优化到 95%(P=0.95):
- 4 核:
S = 1/(0.05+0.95/4) ≈ 3.48x - 32 核:
S = 1/(0.05+0.95/32) ≈ 12.55x
结论:优化串行部分(从 20% 到 5%)比堆 28 个核效果好得多(12.55x vs 4.44x)。
Q2:功耗墙与多核
P = C × V² × f。主频 f 从 3GHz → 6GHz,要稳定还需要提高电压 V(否则信号失真),因此功耗至少翻 4-8 倍。500W+ 的 CPU 无法用常规风冷散热,这就是"功耗墙"。
big.LITTLE:日常任务跑小核(低功耗 A55 ~0.5W),重任务跑大核(A78 ~2W),平均功耗远低于全大核。更关键的是——大小核可以同时跑,小核处理后台任务时大核休眠,总吞吐不变但功耗减半。
Q3:冯·诺依曼瓶颈的三种对策
| 对策 | 原理 | 代价 |
|---|---|---|
| 缓存层级(L1/L2/L3) | 把热数据拉到 CPU 门口 | 晶体管面积 + 功耗 |
| 预取(Prefetch) | 推测未来访问,提前搬数据 | 误预取浪费带宽 |
| 乱序执行 | 等数据时先执行其他指令 | 复杂度 + Spectre 类安全漏洞 |
| 多通道内存 | 并行访问多个内存通道 | 主板布线 + 成本 |
Q4:给老板的结构化回答
"老板,这次事故的根因不是硬盘坏,是我们对 I/O 路径没有监控。我建议三件事:① 核心接口加 I/O 延迟埋点,磁盘慢的时候接口自动降级而不是全挂(用内存缓存兜底);② 梳理所有"读小文件"的地方改成预读或合并读,减少 I/O 次数;③ 每次发布前跑一遍 I/O 压测。这样下次磁盘慢之前我们就能先知道。"
# 第 02 篇·计算机存储器的原理
# 基础思考题
Q1:速度台阶
| 层级 | 延迟(ns) | 相邻差距 |
|---|---|---|
| 寄存器 | ~0.3ns | 基准 |
| L1 Cache | ~1ns | 3x |
| L2 Cache | ~5ns | 5x |
| L3 Cache | ~15ns | 3x |
| 内存 DRAM | ~100ns | 6-7x |
| SSD | ~100,000ns | 1000x |
| HDD | ~10,000,000ns | 100x |
"数量级跃升"的根本原因:物理介质变了。寄存器→L3 是 SRAM,DRAM 是电容,SSD 是 NAND,HDD 是机械。每换一种物理介质,延迟就跳一个数量级。
Q2:SRAM vs DRAM
| 维度 | SRAM | DRAM |
|---|---|---|
| 晶体管/bit | 6 个 | 1T+1C |
| 速度 | ~1ns | ~100ns |
| 功耗 | 高(始终通电保持状态) | 低(只需刷新) |
| 成本 | 极高 | 低 |
| 密度 | 低 | 高 |
SRAM 不能做 8GB 内存条的原因:8GB 的 SRAM 需要 8×8×6 ≈ 3840 亿个晶体管,面积和功耗大到不可接受。
Q3:断电就丢的是谁
- 会丢:CPU 寄存器、L1 Cache、DDR 内存、Page Cache、eMMC Write Buffer(易失性)
- 不丢:NAND 闪存颗粒(SSD/eMMC 的核心存储)、机械硬盘盘片(磁性介质)
陷阱:eMMC 的 Write Buffer 是 SRAM,断电即丢;但 NAND 颗粒是非易失的。这就是为什么突然断电可能导致"写了一半"的数据丢失。
Q4:局部性原理举例
- 时间局部性:
for(i=0;i<N;i++) sum+=arr[i]中的sum变量——每次迭代都被访问 - 空间局部性:
for(i=0;i<N;i++) arr[i]=0——地址连续,Cache Line 预取全覆盖 - 违反局部性:跳表/链表随机访问——
node=node->next每次跳转地址不可预测,几乎每次 Cache Miss
# 进阶思考题
Q1:本地 KV 存储方案权衡
| 方案 | 不丢数据 | 不卡主线程 | 适用场景 |
|---|---|---|---|
commit() | ✅(同步 fsync) | ❌ 卡主线程 | 关键配置,量小 |
apply() | ❌(只到 Page Cache) | ✅ | 高频、可容忍少量丢失 |
mmap+msync | ✅(msync 可控) | ✅ | 中等量、需要零拷贝 |
| SQLite WAL | ✅(WAL 先写日志) | ✅ | 需要事务、查询能力 |
最佳实践:高频写用 WAL(先写顺序日志,再异步合并),读用 mmap(零拷贝)。
Q2:apply() vs commit() 的底层
两者最终都用 fsync 或等效机制落盘。区别在什么时候调用 fsync:
commit():写 Page Cache → 立即fsync→ 等磁盘确认 → 返回(同步)apply():写 Page Cache → 返回 → 后台异步fsync(异步)
最终落盘没有本质区别——都是 fsync。但如果 apply() 还没执行到后台 fsync 就断电,数据就丢了。
Q3:mmap 不适合的场景
- 小文件频繁改写:mmap 的页映射粒度为 4KB,改写 1 字节也要刷整页
- 网络文件系统(NFS):msync 在网络文件系统上的语义不保证
- 多进程并发写同一文件:需要额外的同步机制,不如用
pwrite - 数据量超过虚拟地址空间:32 位程序最多 4GB 地址空间
Q4:MySQL 如何用 Page Cache 且不丢数据
MySQL(InnoDB)的保证链条:WAL(Write-Ahead Log)→ fsync 日志文件 → 数据写入 Page Cache → 异步刷盘。即使断电,重启后从 WAL 日志重放恢复。
innodb_flush_log_at_trx_commit=1:每次事务提交都 fsync redo log(最安全)- Double Write Buffer:防止页部分写入(页=16KB,磁盘原子写入可能是 512B)
Q5:硬件用伪 LRU 而软件用真 LRU
硬件约束:SRAM 面积极其昂贵,每个 Cache Line(64B)如果用真 LRU 记录访问顺序需要的位数太多。伪 LRU 用二叉树实现,8 路组相联只需 7 bit。软件层(Redis)内存大,可以维护双向链表 + 哈希表实现精确 LRU,开销可接受。
# 第 03 篇·计算机基础 CPU 设计
# 基础思考题
Q1:count++ 拆成几条指令?
mov eax, [count_addr] ; ① 从内存取 count 到寄存器(Load)
add eax, 1 ; ② ALU 加 1
mov [count_addr], eax ; ③ 写回内存(Store)
三条指令,每条之间有"窗口期"。如果两个线程各自执行了步骤①读到相同的旧值,各自加 1 后写回——两次写回相同的"新值",丢了一次加 1。不是原子的因为 Load-Add-Store 是三个独立的指令周期。
Q2:CISC vs RISC——Intel 为什么不一刀切?
保留 CISC 外壳有三个理由:
- 二进制兼容性:几十年的 x86 应用不能重新编译——AMD64 兼容 IA-32 是 x86 最核心的资产
- 代码密度:CISC 变长指令平均代码更紧凑(I-Cache 压力小)
- μops 融合:复杂指令解码时可以在 μops 层做融合优化(如
load+add → 一条 μop),反而比纯 RISC 更高效
Q3:功耗墙
P ∝ V² × f。f 翻倍需要提高 V 来驱动更高的开关速度(否则上升沿不够陡),导致功耗增长远超线性。3GHz→6GHz,功耗≈4-8x。而散热能力有物理上限(风冷<200W,水冷<500W)。
Q4:MESI 四状态对号入座
| 场景 | MESI 状态 |
|---|---|
| 刚 new 出来的对象 | E(Exclusive)——只有当前核有,且与内存一致 |
| 只读共享的配置 | S(Shared)——多核读取,与内存一致 |
| 正在被当前线程高频修改的计数器 | M(Modified)——只有当前核有,已修改,内存过期 |
| 被其它线程写过的过期数据 | I(Invalid)——当前核的副本已失效,下次访问要重新从其他核/内存取 |
Q5:伪共享判定
- 两个线程写同一个对象的两个
long→ 伪共享(两个字段在同一缓存行 64B 内) - 两个线程写
long[]的不同下标 → 如果下标差<8(64/8)则伪共享 - 两个线程写不同对象的各自
long字段(对象连续分配)→ 伪共享(对象在堆上连续分配,字段间距可能 <64B)
核心判断:两个写发生的物理地址间距 < 64 字节 = 伪共享。
# 进阶思考题
Q1:CAS 不是"免费的原子"
synchronized 底层是 monitorenter → 对象头锁标记,持锁失败→线程挂起(OS 调度开销)。AtomicLong 的 CAS 不自旋挂起,但在高竞争下 lock cmpxchg 指令反复失败→自旋,这时候:
- 每个 CAS 都触发 MESI 的 Invalidate 消息,缓存行在多核间弹跳——每次 CAS 的实际延迟从 ~10ns 变成 ~200ns
- 10 个线程同时 CAS 时 9 个失败、全部重试——CPU 在空转
此时 synchronized 更好:挂起线程释放 CPU,其他线程可以做有用工作。
Q2:LongAdder 什么时候比 AtomicLong 慢?
- 低并发(<2 线程):Cell 数组的初始化开销 + 额外内存 = 净损耗
- 频繁调用
sum():需要遍历所有 Cell 并原子累加(高竞争下 cells 可能很多) - 内存占用:
@Contended让每个 Cell 占至少 64B(甚至 128B),32 个 Cell = 4KB
Q3:volatile 和内存屏障
x86:TSO(Total Store Order)模型,只需 StoreLoad 屏障(mfence 或 lock 前缀)。
ARM:弱内存模型,需要 dmb 全屏障。
Java volatile 在 x86 上编译为 lock addl $0, (%rsp)(一种"空操作"的 lock 前缀,达到 StoreLoad 屏障效果)。
# 第 04 篇·系统 CPU 缓存的设计
# 基础思考题
Q1:为什么遍历顺序影响性能?
按行遍历 for(i) for(j) arr[i][j]:访问地址连续(arr[i][0], arr[i][1], ...),每次 Cache Miss 后缓存行预取 64B(16 个 int),后续 15 个命中——命中率 94%。
按列遍历 for(j) for(i) arr[i][j]:访问地址跳跃(每次跳一行,典型 40KB),每跳都 Cache Miss,且下次不会被预取——命中率接近 0%。
Q2:全相联 vs 组相联——为什么不高比例用全相联?
| 全相联 | 组相联(8路) | |
|---|---|---|
| 冲突缺失 | 0% | 极少 |
| 查找硬件 | 每行一个比较器(N个) | 每组 8 个比较器 |
| 能耗 | 极高 | 低 |
全相联的"每个地址跟所有行同时比较"需要 N 个比较器并行工作——N=1024 时需要 1024 个比较器,功耗和面积爆炸。8 路组相联在冲突率和硬件成本之间找到最优平衡。
Q3:Cache Line 64B——小了浪费、大了浪费
小(32B):突发传输未充分利用(DDR Burst Length=8 × 8B=64B);空间局部性覆盖不足。 大(128B):误预取更多无用数据,污染缓存;真共享冲突(两个核访问不同变量但落同一行概率更高)。
Q4:矩阵乘法优化原理
- 循环交换:让最内层循环按行而非按列访问——空间局部性
- 分块(Tiling):把大矩阵切成 L1 能装下的小块(如 64×64),小块内的数据在 L1 被反复用——时间局部性
- SIMD:一次加载 4 个 float,一次乘加 4 个——指令级并行
- 预取:
_mm_prefetch提前拉下一块——隐藏内存延迟
# 第 05 篇·计算机输入输出设备
# 基础思考题
Q1:四种 I/O 控制方式的区别
| 方式 | CPU 参与 | CPU 利用率 | 适用 |
|---|---|---|---|
| 程序查询 | 全程轮询 | 0%(空转) | 极其简单的嵌入式 |
| 中断驱动 | 响应中断 | 高 | 键盘、鼠标 |
| DMA | 只发指令 | 最高 | 硬盘、网卡大块传输 |
| 通道 | 几乎不参与 | 最高 | 大型机专用 I/O |
Q2:为什么要有 I/O 接口?
直接连 CPU 总线会遇到四个不匹配:
- 速度:CPU GHz vs 外设 kHz-Hz——缓冲暂存
- 格式:串行(USB)vs 并行(CPU 总线)——串并转换
- 电平:CPU 1.2V vs 外设 5V/12V——电平转换
- 时序:同步总线 vs 异步外设——握手协议
Q3:中断处理流程
请求→响应(保存 PC/PSW 入栈)→查中断向量表(IDT,x86 有 256 个中断向量)→执行 ISR(关中断或可嵌套)→恢复现场并返回(IRET 指令恢复 PC/PSW)
Q4:DMA 和 CPU 同时访问内存怎么协调?
通过总线仲裁:DMA 控制器在 CPU 不访问内存时"偷"用总线(Cycle Stealing)。更现代的方案是 DMA 通过独立的内存通道(如 IOMMU),不和 CPU 抢总线。
# 第 06 篇·计算机总线系统设计
# 基础思考题
Q1:总线仲裁三种方式
| 方式 | 原理 | 优缺点 |
|---|---|---|
| 菊花链 | 设备串联,优先级=物理位置 | 简单但前级故障影响后级 |
| 集中仲裁 | 独立仲裁器统一分配 | 公平但单点瓶颈 |
| 分布式自举 | 各设备有独立请求线+仲裁逻辑 | 灵活但布线复杂 |
PCIe 用的是集中仲裁 + 交换机转发——Root Complex 作为仲裁者,Switch 做包交换。
Q2:PCIe 的 lane 多路复用
PCIe lane 是一对差分信号线(TX+/TX-,RX+/RX-)。x1=1 lane(~1GB/s),x16=16 lane 并行(~16GB/s per direction)。Lane 数量独立决定带宽,不共享时钟。这就是为什么显卡用 x16 而网卡用 x1。
Q3:QPI/UPI vs DMI
| QPI/UPI | DMI | |
|---|---|---|
| 连接 | CPU-to-CPU | CPU-to-PCH(南桥) |
| 带宽 | ~10.4GT/s | ~4GT/s |
| 延迟 | 极低(~40ns) | 较高 |
| 作用 | 多路服务器互连 | 连接外围低速设备 |
# 第 07 篇·计算机指令编程原理
# 基础思考题
Q1:寻址方式举例
mov eax, 42 ; 立即寻址(值在指令里)
mov eax, [0x600000] ; 直接寻址(地址在指令里)
mov eax, [ebx] ; 寄存器间接寻址(地址在寄存器里)
mov eax, [ebx+4] ; 基址寻址(基地址+偏移)
mov eax, [ebx+esi*4] ; 变址寻址(数组访问)
Q2:编译四阶段
gcc hello.c -o hello:
- 预处理:
cpp展开#include、#define→.i - 编译:
cc1生成汇编→.s - 汇编:
as生成目标文件→.o - 链接:
ld链接库函数→可执行文件
Q3:定长 vs 变长指令
| RISC(ARM 32位) | CISC(x86 1-15字节) | |
|---|---|---|
| 解码 | 极简(固定格式) | 复杂(先判断长度) |
| 代码密度 | 低 | 高 |
| 流水线 | 简单 | 复杂 |
| 取指 | 一次一个 | 先取 16B 再找边界 |
# 第 08 篇·计算机程序如何执行
# 基础思考题
Q1:五种数据冒险及 Forwarding 解决
add r1, r2, r3 ; r1 = r2 + r3
sub r4, r1, r5 ; r4 = r1 - r5 ← 读 r1,但上条还在 EX 阶段
无 Forwarding:等 add 写回后 sub 再取(浪费 3 个周期)。
有 Forwarding:add 的 ALU 输出直接旁路到 sub 的 ALU 输入——节省 3 个周期。
Q2:2 位饱和计数器
状态机:强不跳(00) ↔ 弱不跳(01) ↔ 弱跳(10) ↔ 强跳(11)。预测错一次不变——需要连续两次错才翻转。循环中的分支(如 for 的结束判断)会在最后两次"不跳→跳→不跳"中保持高准确率。
Q3:乱序执行 vs 顺序提交
乱序执行让指令以"数据就绪"顺序执行,减少流水线停顿。但必须顺序提交(ROB 确保按程序序更新寄存器/内存)——否则中断发生时 CPU 状态不完整,无法恢复。ROB 同时承担了"暂存结果"和"保证精确异常"两个职责。
# 第 09 篇·计算机内存设计原理
# 基础思考题
Q1:虚拟内存解决什么问题?
| 问题 | 解决方案 |
|---|---|
| 地址冲突(两进程用同一地址) | 各自独立虚拟地址空间,映射到不同物理页 |
| 内存不够用 | 不常用页 swap 到磁盘,用到时换回 |
| 内存碎片 | 分页将内存切成固定 4KB 块,无外部碎片 |
| 非法访问 | 页表项权限位(R/W/U/S)MMU 检查 |
Q2:分段 vs 分页
| 分段 | 分页 | |
|---|---|---|
| 单位 | 可变大小段 | 固定 4KB 页 |
| 外部碎片 | 有(严重) | 无 |
| 内部碎片 | 无 | 少量(最后一页) |
| 共享 | 困难 | 容易(页级别共享) |
Q3:缺页异常处理流程
- CPU 访问虚拟地址,TLB Miss → 查页表 → 页不在内存(Present=0)
- CPU 触发 #PF(Page Fault)异常
- OS 异常处理程序:检查访问合法性 → 选一个物理页(可能需要换出旧页)→ 从磁盘读入 → 更新页表项(Present=1)
- 返回重新执行触发指令——这次页在内存,成功
Q4:TLB 的核心作用
TLB 是页表的 Cache,缓存最近使用的虚拟→物理地址映射。一次 TLB Miss 要走完四级页表(4 次内存访问 ~400ns),而 TLB 命中只需 ~1ns。TLB 命中率从 99% 降到 80%,程序性能下降 ~9%。
# 第 10 篇·计算机二进制和字节
# 基础思考题
Q1:0.1 + 0.2 为什么不是 0.3?
0.1(十进制)= 0.0001100110011...(二进制,无限循环)。IEEE 754 单精度 - 23 位尾数截断,丢失精度。累加时误差一步步放大。本质是有限的二进制位无法精确表示无限的十进制小数。
Q2:补码为什么让减法变成加法?
补码定义:-X = 2^n - X(模 2^n 运算)。A - B = A + (2^n - B) = A + (-B 的补码)——CPU 用同一个加法器电路同时支持加法和减法。一套电路,两套语义。
Q3:UTF-8 编码规则
| Unicode 范围 | UTF-8 字节数 | 格式 |
|---|---|---|
| U+0000-U+007F(ASCII) | 1 | 0xxxxxxx |
| U+0080-U+07FF | 2 | 110xxxxx 10xxxxxx |
| U+0800-U+FFFF | 3 | 1110xxxx 10xxxxxx 10xxxxxx |
| U+10000-U+10FFFF | 4 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
"汉"(U+6C49)→ 0110 1100 0100 1001 → 11100110 10110001 10001001 → E6 B1 89
Q4:大端 vs 小端——什么时候会踩坑?
- 网络通信:IP/TCP 头用网络字节序(大端),主机可能是小端→
htonl/ntohl转换 - 跨平台二进制文件:x86 写的文件 ARM 上读→字节序反转
- union 取低字节:小端下
union { int i; char c; }的c是i的最低字节
# 第 11 篇·计算机异常处理机制
# 基础思考题
Q1:四种异常的区别
| 类型 | 触发源 | 同步/异步 | 可恢复? | 返回位置 | 例子 |
|---|---|---|---|---|---|
| 中断 | I/O 设备 | 异步 | ✅ | 下一条指令 | 键盘按键、网卡收包 |
| 陷阱 | int 0x80 | 同步 | ✅ | 下一条指令 | 系统调用 |
| 故障 | 指令错误 | 同步 | 可能 | 当前指令(重执行) | 缺页异常、除零 |
| 终止 | 硬件故障 | 同步 | ❌ | 不返回 | 双故障、机器检查 |
Q2:缺页异常全流程
同 §09 Q3。
Q3:Stack Canary 原理
函数入口时在栈上返回地址旁边放一个随机值(Canary),函数返回前检查这个值是否被改变。如果被改变说明发生了栈溢出(覆盖了返回地址),立即终止进程。利用异常机制实现安全防护。
Q4:信号的默认处理
| 信号 | 默认动作 |
|---|---|
| SIGINT(Ctrl+C) | 终止进程 |
| SIGSEGV | 终止 + core dump |
| SIGKILL | 杀死(不可捕获) |
| SIGCHLD | 忽略 |
# 第 12 篇·计算机 I/O 操作和原理
# 基础思考题
Q1:select / poll / epoll 区别
| select | poll | epoll | |
|---|---|---|---|
| fd 数量 | 1024 硬限制 | 无限制 | 无限制 |
| 内核数据结构 | 3 个 bitmap | 链表 | 红黑树+就绪链表 |
| 获取就绪 fd | O(n) 遍历 | O(n) 遍历 | O(1) 从就绪链表取 |
| 每次调用传入 | 全部 fd 集合 | 全部 fd | 只传 epoll fd |
| 内核修改 | 大量复制 | 大量复制 | 事件触发 |
Q2:零拷贝 vs 传统拷贝的数据流
传统 read+write:磁盘→内核缓冲区→用户缓冲区→内核 Socket 缓冲区→网卡(4 次拷贝 + 4 次上下文切换)
sendfile:磁盘→内核缓冲区→网卡(2 次 DMA + 2 次上下文切换,0 次 CPU 拷贝)
Q3:阻塞/非阻塞 vs 同步/异步的区别
| 同步 | 异步 | |
|---|---|---|
| 阻塞 | BIO:read() 到数据才返回 | — |
| 非阻塞 | NIO epoll:内核通知可读,应用自己 read | AIO io_uring:内核读完数据直接给到缓冲区 |
根本区别:同步=应用主动取数据,异步=内核送数据上门。
Q4:Reactor vs Proactor
| Reactor | Proactor | |
|---|---|---|
| 谁负责读数据 | 应用自己 read() | 内核读完通知"数据在 X" |
| 代表技术 | epoll + NIO | io_uring / IOCP |
| 回调时机 | "fd 可读了" | "数据已在缓冲区" |
| CPU 开销 | 应用做拷贝 | 内核做拷贝 |
# 自测:全书掌握度检查
读完 12 篇答案后,自己测试:
- 你写的
a = b + c从 C 代码到屏幕上出结果,经过了哪些部件?每一步耗时量级? - 为什么
LinkedList遍历比ArrayList慢几倍?与哪两个硬件特性有关? - 多线程下
count++有哪三种写法(synchronized/AtomicLong/LongAdder)?为什么 LongAdder 最快? 0.1 + 0.2为什么不是 0.3?金融计算该用什么?- 你服务的 QPS 卡在 3000,排查顺序是什么?
答案在本总结文件的 §13(贯通三句话)到 §16(自测清单)中。答不出任一题,回到对应篇章的速通卡重读。