带你走进Linux内核源码中最常见的数据结构之「mutex」
内容提要
互斥锁是用于多线程编程的机制,防止多线程同时对同一公共资源进行读写操作。互斥锁通过加锁和解锁操作实现线程同步。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时,多个线程对共享变量的操作可能导致数据错误,结果不符合预期。