1. 并发编程基础

并发编程是内核性能调优的基石。在深入读写锁与 RCU 之前,必须精确理解三个核心概念:竞态条件、临界区以及并发与并行的区别。这些概念直接决定了锁机制的设计思路与性能边界。

1.1 竞态条件(Race Condition)

定义:当多个执行单元(线程/进程/CPU 核)同时访问共享资源,且最终结果依赖于这些执行单元的精确时序时,就产生了竞态条件。竞态条件会导致数据不一致、系统崩溃或安全漏洞。

核心特征: 结果不可预测,且难以通过简单测试复现。竞态条件在低负载下可能“正常”,在高并发下突然爆发。

典型竞态场景(检查-使用模式):

// 伪代码:两个线程同时执行
if (counter > 0) {          // 步骤1:检查
    counter--;               // 步骤2:使用
    // 预期:counter 从 1 减到 0
    // 实际:两个线程都通过检查,counter 变成 -1
}

上述代码中,步骤1和步骤2之间不是原子操作。当线程A检查完 counter > 0 后,线程B也检查并通过,随后两个线程都执行 counter--,导致 counter 被多减一次。

1.2 临界区(Critical Section)

定义:临界区是访问共享资源的代码片段,在同一时刻只允许一个执行单元进入。临界区必须满足互斥(Mutual Exclusion)原则。

临界区的三个关键属性:

属性 说明 违反后果
互斥 最多一个线程在临界区内执行 数据损坏、竞态条件
前进 没有线程在临界区时,请求进入的线程必须被允许进入 死锁、活锁
有限等待 线程不能无限等待进入临界区 饥饿、优先级反转
调优视角: 临界区的粒度直接影响性能。临界区越短,锁竞争越小,但设计复杂度越高。读写锁和 RCU 的核心目标就是缩小临界区或消除读端临界区。

1.3 并发与并行的区别

这两个术语常被混用,但在并发编程中含义截然不同:

维度 并发(Concurrency) 并行(Parallelism)
定义 多个任务在逻辑上同时执行(交替执行) 多个任务在物理上同时执行(真正同时)
硬件要求 单核 CPU 即可实现(时间片轮转) 必须多核 CPU 或多处理器
关注点 任务调度、资源访问控制 计算吞吐、负载均衡
典型场景 单核服务器处理多个网络连接 多核 CPU 并行处理矩阵乘法
竞态风险 存在(时间片切换点) 极高(真正同时访问)
常见误区: 并发不一定是并行,但并行一定是并发。Linux 内核中,即使只有一个 CPU,也可能因为中断、软中断、抢占等机制产生并发竞态。多核环境下,并行竞态更加频繁且难以调试。

1.4 概念关系图

下图展示了竞态条件、临界区与并发/并行之间的逻辑关系:

竞态条件、临界区与并发并行关系图 竞态条件、临界区与并发/并行关系图 并发(Concurrency) 逻辑同时:单核时间片轮转 并行(Parallelism) 物理同时:多核真正同时执行 多个执行单元访问同一共享资源 竞态条件(Race Condition) 临界区(Critical Section) 解决方案:原子操作 → 自旋锁 → 读写锁 → RCU(本课程核心)

1.5 内核中的并发来源

Linux 内核中,竞态条件可能来自以下五种并发源:

  1. 中断处理:硬件中断异步触发,与当前执行流并发。
  2. 软中断 / tasklet:在中断上下文或特定 CPU 上异步执行。
  3. 内核抢占:高优先级进程抢占当前进程的执行。
  4. 多处理器并行:多个 CPU 同时执行内核代码。
  5. 进程间通信:信号、共享内存等导致的同步问题。
实战要点: 在编写内核模块或驱动时,必须假设任何时刻都可能被中断、抢占或并行执行。使用 spin_lock_irqsave() 等变体正是为了同时屏蔽中断和抢占,确保临界区的绝对互斥。

1.6 本章小结

概念 一句话记忆 对性能调优的意义
竞态条件 结果依赖时序,不可预测 必须通过同步机制消除
临界区 一次只让一个人进的代码段 临界区越小,锁竞争越低
并发 vs 并行 并发是逻辑同时,并行是物理同时 并行场景下锁竞争更激烈,需要更高效的同步机制