学习笔记二十二—— 并发五大常见陷阱

⚠️ 并发五大常见陷阱


目录

  1. 数据竞争 (Data Race)
  2. 死锁 (Deadlock)
  3. 竞态条件 & 饿死现象 (Race Condition & Starvation)
  4. 悬挂指针 (Dangling Pointer)
  5. 重复释放 (Double Free)
  6. 开发自查清单

1. 数据竞争 (Data Race)

  • 专业定义

    两个及以上线程在缺乏同步的情况下同时访问同一地址,并且至少有一个线程执行写操作。

  • 通俗比喻

    两个人同时在同一张 Excel 表里改同一个单元格,最后单元格的内容谁也说不准。

  • 危害

    状态错乱、随机崩溃、难以复现。

  • 常见场景

    • 全局计数器 counter++
    • 缓存读写(读线程没加锁)
  • 极简示例(C11)

    c 复制代码
    int counter = 0;
    
    void* add(void* _) {
        for (int i = 0; i < 1000000; ++i) counter++;
        return NULL;
    }
  • 发现方法

    ThreadSanitizer、Helgrind、Rust 编译器借用检查。

  • 避免策略

    互斥锁 Mutex、原子类型 Atomic*、消息传递 (channel),"不共享即不竞争"。


2. 死锁 (Deadlock)

  • 专业定义

    多线程因为循环等待资源导致相互阻塞且永不释放。

  • 通俗比喻

    A 把门钥匙握在手里等 B 的车钥匙,B 把车钥匙握在手里等 A 的门钥匙------谁也别想走。

  • 死锁四必要条件

    互斥、占有并等待、不可抢占、循环等待。

  • 典型示例(Java)

    java 复制代码
    synchronized(lockA) {
        synchronized(lockB) { /* ... */ }
    }
    
    // 另一线程
    synchronized(lockB) {
        synchronized(lockA) { /* ... */ }
    }
  • 发现方法
    jstack 看线程栈、Rust cargo-deadlock、Go 运行时死锁检测。

  • 避免策略

    固定锁顺序、try_lock + 超时回退、细粒度锁、用 Channel / Actor 模式取代共享锁。


3. 竞态条件 & 饿死现象

3.1 竞态条件 (Race Condition)
  • 专业定义

    程序的正确性依赖于多个事件的执行顺序,而该顺序又未受控制。

  • 通俗比喻

    检查房门没锁 → 去拿快递 → 回来发现小偷已进屋------检查‑后‑执行窗口被抢占。

  • 场景示例(Shell)

    bash 复制代码
    if [ ! -e /tmp/mydir ]; then
        mkdir /tmp/mydir   # 另一个进程可能在检查后立即创建
    fi
  • 修复关键

    将"检查 + 创建"做成一个原子操作 (加锁,或用 mkdir -p 让系统保证原子性)。

3.2 饿死现象 (Starvation)
  • 定义

    线程长期得不到 CPU 时间片或资源,处于"活着却干不了活"状态。

  • 典型场景

    • 读写锁:大量读锁使写锁一直被饿死
    • 严格优先级调度:低优先级线程永远排不到
  • 缓解

    公平锁、优先级继承、动态调度策略。


4. 悬挂指针 (Dangling Pointer)

  • 专业定义

    指针仍指向一块已释放或作用域结束的内存区域。

  • 通俗比喻

    拿着老房子的钥匙,房子已被拆迁,再开门只会撞墙。

  • 示例(C)

    c 复制代码
    int *p;
    {
        int x = 42;
        p = &x;
    }          // x 生命周期结束
    printf("%d\n", *p);  // 悬挂访问
  • 解决思路

    RAII / 智能指针 / Rust 所有权模型自动禁止悬挂引用。


5. 重复释放 (Double Free)

  • 专业定义

    对同一内存块调用两次释放操作。

  • 通俗比喻

    电影票撕过一次又被检票员再撕一次------第二次可能撕到别人的票。

  • 示例(C)

    c 复制代码
    char *buf = malloc(100);
    free(buf);
    free(buf);          // 第二次释放
  • 危害

    崩溃;更严重时可破坏堆结构,被黑客利用执行任意代码。

  • 防范

    设置指针为 NULL、使用智能指针(unique_ptr / Rust 所有权自动回收)、开启 AddressSanitizer。


6. 开发生命线------五步自查

  1. 代码审查:重点看"检查‑后‑执行"与手动内存管理片段。
  2. 静态分析:开启编译器最大警告,IDE 并发检查。
  3. 动态探测:CI 跑 AddressSanitizer / ThreadSanitizer / Valgrind。
  4. 运行监控:锁等待时间、线程阻塞时长、队列深度。
  5. 语言特性先行 :能用 RAII / 智能指针 / 所有权就别手写 malloc/free

一句话总结
"共享越少,风险越小;让编译器和工具兜底,用良好设计把错堵在出生之前。"

相关推荐
r i c k1 小时前
数据库系统学习笔记
数据库·笔记·学习
野犬寒鸦2 小时前
从零起步学习JVM || 第一章:类加载器与双亲委派机制模型详解
java·jvm·数据库·后端·学习
shandianchengzi2 小时前
【小白向】错位排列|图文解释公考常见题目错位排列的递推式Dn=(n-1)(Dn-2+Dn-1)推导方式
笔记·算法·公考·递推·排列·考公
浅念-3 小时前
C语言编译与链接全流程:从源码到可执行程序的幕后之旅
c语言·开发语言·数据结构·经验分享·笔记·学习·算法
ZH15455891313 小时前
Flutter for OpenHarmony Python学习助手实战:API接口开发的实现
python·学习·flutter
爱吃生蚝的于勒3 小时前
【Linux】进程信号之捕捉(三)
linux·运维·服务器·c语言·数据结构·c++·学习
奶茶精Gaaa3 小时前
工具分享--F12使用技巧
学习
The森3 小时前
Linux IO 模型纵深解析 01:从 Unix 传统到 Linux 内核的 IO 第一性原理
linux·服务器·c语言·经验分享·笔记·unix
tq10863 小时前
Skills 的问题与解决方案
笔记
三水不滴3 小时前
有 HTTP 了为什么还要有 RPC?
经验分享·笔记·网络协议·计算机网络·http·rpc