当前位置: 首页 > news >正文

Linux并发与竞争:从生活例子到内核实战

Linux并发与竞争:从生活例子到内核实战

一、并发与竞争:多车道公路的交通问题

想象一条四车道的高速公路(多核CPU),所有车辆(线程/进程)都想通过同一个收费站(共享资源)。如果没有交通规则(同步机制),就会发生:

  1. 数据竞争:两辆车同时抢一个收费口,导致碰撞(数据损坏)
  2. 死锁:四辆车互相阻挡,形成十字死锁(deadlock)
  3. 饥饿:大货车长期占用车道,小车永远过不去(starvation)

二、原子操作:不可分割的收费卡

2.1 原子操作原理

就像高速公路的ETC系统,收费过程(i++这样的操作)必须一次性完成,不能被其他车辆打断。内核提供两类原子操作:

2.2 原子整形操作

atomic_t v = ATOMIC_INIT(0);  // 初始化原子变量
atomic_inc(&v);               // v++(不可打断)
int val = atomic_read(&v);    // 安全读取

生活例子:银行金库的黄金数量统计,必须整块计算,不能出现"数到一半被其他人拿走"的情况。

2.3 原子位操作

unsigned long word = 0;
set_bit(0, &word);  // 第0位置1(类似|=操作)
clear_bit(1, &word); // 第1位清0

适用场景:设备寄存器位操作、标志位管理。

三、自旋锁:不停旋转的收费站岗亭

3.1 自旋锁特性

  • 忙等待:获取不到锁时,CPU会"空转"(就像司机在收费口不停探头张望)
  • 短期持有:适合锁持有时间短于线程切换开销的情况
DEFINE_SPINLOCK(my_lock);  // 定义锁spin_lock(&my_lock);       // 获取锁
/* 临界区操作 */           // 只有一辆车能通过
spin_unlock(&my_lock);     // 释放锁

3.2 锁的类型选择指南

锁类型生活类比适用场景
普通自旋锁普通收费岗亭单核CPU/非中断上下文
IRQ自旋锁应急车道专用岗亭中断上下文共享数据
读写自旋锁ETC与人工通道分离读多写少场景(如配置表)

3.3 死锁预防(交通规则)

  1. 避免嵌套锁:不要在一个锁内获取另一个锁
  2. 统一顺序:多个锁按固定顺序获取
  3. 超时机制spin_trylock_irqsave尝试获取锁

四、信号量:停车场剩余车位显示

4.1 信号量特点

  • 允许睡眠:获取不到资源时,线程可以休眠(不像自旋锁那样空转)
  • 资源计数:可以管理多个同类资源(如停车场剩余车位)
struct semaphore sem;
sema_init(&sem, 5);  // 初始化5个车位down(&sem);          // 获取车位(没有就睡觉)
/* 停车操作 */
up(&sem);            // 释放车位

生活场景

  • 消费者生产者问题(停车场进出车辆)
  • 打印机池管理(多个打印任务排队)

五、互斥体:单间厕所的门锁

5.1 互斥体特性

  • 唯一访问:同一时刻只有一个线程能进入临界区
  • 可睡眠:比自旋锁更适合长时间持有的场景
struct mutex my_mutex;
mutex_init(&my_mutex);mutex_lock(&my_mutex);
/* 临界区操作 */      // 如厕所单间使用
mutex_unlock(&my_mutex);

5.2 与自旋锁的对比

特性自旋锁互斥体
等待方式CPU空转线程休眠
开销低(无上下文切换)高(需调度)
持有时间短(纳秒级)长(毫秒级)
中断上下文可用不可用

六、实战选择流程图

graph TDA[需要保护共享资源?] -->|是| B{临界区执行时间}B -->|短(<10us)| C[自旋锁]B -->|长(>10us)| D{是否在中断上下文}D -->|是| CD -->|否| E[互斥体]A -->|否| F[无需同步]C --> G{是否需要读写分离}G -->|是| H[读写自旋锁]G -->|否| I[普通自旋锁]E --> J{资源是否可计数}J -->|是| K[信号量]J -->|否| E

七、常见错误案例

7.1 错误示范:中断中误用互斥体

// 错误代码(中断上下文不能睡眠)
irq_handler() {mutex_lock(&lock);  // 可能引发系统崩溃/* 操作硬件寄存器 */mutex_unlock(&lock);
}// 正确做法(使用自旋锁)
irq_handler() {spin_lock_irqsave(&lock, flags);/* 操作硬件 */spin_unlock_irqrestore(&lock, flags);
}

7.2 错误示范:忘记释放锁

void func() {mutex_lock(&lock);if (error) {return; // 直接返回导致锁未释放!}mutex_unlock(&lock);
}// 正确做法(使用goto统一释放)
void func() {mutex_lock(&lock);if (error) {goto unlock;}
unlock:mutex_unlock(&lock);
}

八、性能优化技巧

  1. 减小临界区:只把必须保护的代码放在锁内

    // 不好
    mutex_lock(&lock);
    process_data(data);
    save_to_disk(data);
    mutex_unlock(&lock);// 优化后
    mutex_lock(&lock);
    tmp = data; // 只保护数据拷贝
    mutex_unlock(&lock);
    process_data(tmp);
    save_to_disk(tmp);
    
  2. 读写分离:读多写少时用读写锁

    DEFINE_RWLOCK(rwlock);// 读线程
    read_lock(&rwlock);
    /* 只读操作 */
    read_unlock(&rwlock);// 写线程
    write_lock(&rwlock);
    /* 写操作 */
    write_unlock(&rwlock);
    

记住关键原则:锁的粒度要尽可能小,持有时间要尽可能短

相关文章:

  • 从对数变换到深度框架:逻辑回归与交叉熵的数学原理及PyTorch实战
  • 高企复审奖补!2025年合肥市高新技术企业重新认定奖励补贴政策及申报条件
  • 【AI论文】ToolRL:奖励是工具学习所需的一切
  • [创业之路-382]:企业法务 - 企业如何通过技术专利与技术秘密保护自己
  • 深度学习:迁移学习
  • rocky9.4部署k8s群集v1.28.2版本(containerd)(纯命令)
  • ssh启动不了报错
  • leetcode刷题日记——有效的括号
  • python自动化学习六:断言
  • 基于Java+MySQL 实现(Web)日程管理系统
  • 开发了一个b站视频音频提取器
  • Spring Security结构总览
  • 【MySQL】基本查询
  • Android 16强制横竖屏设置
  • 【oql】spark thriftserver内存溢出,使用oql查询导致oom的sql
  • c语言 write函数
  • 3、ArkTS语言介绍
  • 动态规划dp专题-(下)
  • Unreal Niagara制作SubUV贴图翻页动画
  • CVE-2025-32102 | Ubuntu 下复现 CrushFTP telnetSocket接口SSRF
  • 人大法工委:涉核领域还需要有一部统领性的基础法律
  • “两高”发布侵犯知产犯罪司法解释:降低部分犯罪入罪门槛
  • 今年一季度上海离境退税商品销售额7.6亿元,同比增85%
  • 2024年度全国十大考古新发现公布,武王墩一号墓等入选
  • 校友伉俪捐赠10亿元!成立复旦大学学敏高等研究院
  • 洛阳白马寺内的狄仁杰墓挂上公示牌争论尘埃落定?瀍河区文旅局称已确认