带你走进Linux内核源码中最常见的数据结构之「mutex」

💡 原文中文,约5500字,阅读约需13分钟。
📝

内容提要

互斥锁是用于多线程编程的机制,防止多线程同时对同一公共资源进行读写操作。互斥锁通过加锁和解锁操作实现线程同步。mutex占用更多的CPU缓存和内存。mutex与spinlock的区别是spinlock让线程在循环中等待,而mutex允许多个进程轮流分享资源。Linux kernel-5.8使用原子变量owner实现mutex。

🔎

延伸解读

mutex 的代价:内存与缓存占用

文章指出,当前内核中的 struct mutex 是最大的锁之一,在 x86-64 上约 32 字节,比 struct semaphore(24 字节)和 rw_semaphore(40 字节)更大。更大的数据结构意味着占用更多 CPU 缓存和内存。因此,在选用锁原语时,除了语义正确性,也需要权衡内存与缓存开销。

mutex 与 spinlock 的适用场景

文章对比了 mutex 与 spinlock:spinlock 让线程在循环中等待,通常短时间持有,等待时不可睡眠;mutex 允许多个进程轮流分享资源,可以长时间持有,且等待时可以睡眠。因此,当临界区可能阻塞或需要长时间持有时,mutex 更合适;而极短临界区则可能更适合 spinlock。

mutex 获取的三条路径

Linux kernel-5.8 中,mutex 获取分为 fastpath、midpath 和 slowpath。fastpath 通过 cmpxchg 原子操作尝试无竞争获取;midpath 在持有者正在运行且无更高优先级任务时进行乐观自旋,使用 MCS 锁排队;slowpath 则将任务加入等待队列并休眠。这种混合设计让 mutex 在竞争不激烈时也能保持较好性能。

从实验看互斥锁的必要性

文章通过一个 cnt++ 实验说明:未加锁时,两个线程同时操作共享变量,最终 total 为 2478786,远大于预期的 1000000。原因是 cnt++ 对应多条汇编指令,不具备原子性,执行过程可能被打断。加入 pthread_mutex 保护临界区后,total 恢复为 1000000,验证了互斥锁对数据正确性的保障作用。

❓

Q&A

互斥锁的主要功能是什么?

互斥锁用于防止多线程同时对同一公共资源进行读写操作,确保同一时间只有一个临界区域处于运行状态。

mutex与spinlock有什么区别?

mutex允许多个进程轮流分享资源,而spinlock让线程在循环中等待,通常用于短时间持有的锁。

在Linux内核中,mutex是如何实现的?

Linux kernel-5.8使用原子变量owner来追踪mutex的状态,并通过三种路径(fastpath、midpath和slowpath)来获取锁。

使用mutex的缺点是什么?

mutex占用更多的CPU缓存和内存,可能导致性能下降,尤其是在需要严格语义时。

如何在多线程中使用mutex保护临界资源?

在访问临界资源前调用pthread_mutex_lock()加锁,访问完成后调用pthread_mutex_unlock()解锁。

实验中未使用mutex时会出现什么问题?

未使用mutex时,多个线程对共享变量的操作可能导致数据错误,结果不符合预期。

🏷️

标签

➡️

继续阅读