以下是针对这些并发控制概念的通俗解释,通过生活场景类比和代码示例帮助理解:
一、核心概念通俗解释
1. 原子操作(Atomic Operations)
- 生活类比:银行ATM机取钱,系统必须保证"扣款+出钞"这两个操作要么全部完成,要么全部不完成,不能只扣款不出钞。
- 代码作用:保护单个变量(如计数器)的简单操作,确保操作不可分割。
- 示例:
atomic_t counter = ATOMIC_INIT(0); // 初始化原子变量 atomic_inc(&counter); // 安全的自增操作
2. 自旋锁(Spinlock)
- 生活类比:公共卫生间只有1个坑位,你站在门口不断尝试开门(忙等待),直到里面的人出来。
- 代码作用:短时间保护临界区,不会让出CPU(适合中断上下文)。
- 示例:
spinlock_t lock; spin_lock_init(&lock); spin_lock(&lock); // 获取锁 // 操作共享资源... spin_unlock(&lock); // 释放锁
3. 信号量(Semaphore)
- 生活类比:停车场有5个车位,每进一辆车剩余车位减1,车位满时后续车辆排队等待。
- 代码作用:管理多个资源的访问,允许线程休眠等待。
- 示例:
struct semaphore sem; sema_init(&sem, 5); // 初始化5个资源 down(&sem); // 获取资源(若无资源则休眠) // 使用资源... up(&sem); // 释放资源
4. 互斥体(Mutex)
- 生活类比:公司打印机同一时间只能1个人使用,其他人需要排队等待。
- 代码作用:替代二值信号量,更高效地保护单一资源,支持优先级继承(防止低优先级任务长期占用锁)。
- 示例:
struct mutex lock; mutex_init(&lock); mutex_lock(&lock); // 获取锁(可能休眠) // 操作共享资源... mutex_unlock(&lock); // 释放锁
5. 读写锁(Read-Write Lock)
- 生活类比:图书馆允许多人同时阅读同一本书(读锁),但写书评时需独占(写锁)。
- 代码作用:优化读多写少场景,允许多个读者同时访问。
- 示例:
rwlock_t lock; rwlock_init(&lock); // 读者 read_lock(&lock); // 读取数据... read_unlock(&lock); // 写者 write_lock(&lock); // 修改数据... write_unlock(&lock);
二、阻塞 vs 非阻塞
类型 | 行为 | 应用场景 | 代码示例 |
---|---|---|---|
阻塞 | 线程休眠等待资源 | 耗时操作(如等待按键) | wait_event_interruptible() |
非阻塞 | 立即返回结果 | 实时性要求高 | O_NONBLOCK 标志 |
示例对比
// 阻塞读取(用户进程休眠)
ssize_t read(...) {
wait_event(queue, data_ready);
// 读取数据...
}
// 非阻塞读取(立即返回)
ssize_t read(...) {
if (!data_ready)
return -EAGAIN; // 告知用户态"稍后再试"
// 读取数据...
}
三、竞争与并发
- 竞争(Race Condition):多个执行流(线程/中断)同时修改共享资源,导致数据不一致。
示例:两个线程同时执行counter++
,实际可能只增加1次。 - 并发控制:通过上述锁机制消除竞争。
典型场景
- 中断与进程上下文共享数据
- 需用
spin_lock_irqsave()
禁用中断 + 自旋锁
- 需用
- 多核CPU共享资源
- 使用原子操作或
percpu
变量
- 使用原子操作或
四、选择机制的决策树
五、实战练习建议
- 原子操作实验:实现一个多线程安全计数器
- 自旋锁实验:在中断上下文保护共享缓冲区
- 互斥体实验:控制多个进程对同一设备的访问顺序
- 读写锁实验:统计日志文件的读取和写入操作