[001]从操作系统层面看锁的逻辑
从操作系统层面,锁 (Lock) 是一种同步机制,用于控制多个线程或线程对共享资源的访问,防止竞态条件(race condition).常见的锁包括互斥锁(mutex)、读写锁(read-write lock)、自旋锁(spinlock)等。下面逐一分析其原理,从内核实现,CPU指令,调度机制等角度解释。
1.互斥锁(Mutex)
原理
作用:确保同一时刻只有一个线程/进程访问临界区。
实现机制:基于原子操作和睡眠/唤醒机制。
操作系统实现细节
1.用户态尝试加锁(快速路径):
通常用 原子CAS (Compare-And-Swap) 尝试将锁状态从“未占用”变为已经占用。
成功则进入临界区,失败则进入下一步。
2. 内核态挂起(慢路径):
如果锁被占用,则线程被阻塞,进入等待队列(入Linux的futex队列)
等待唤醒时才尝试重新加锁。
3.解锁时唤醒等待线程
解锁线程将锁状态置为 未占用 并唤醒一个或者多个等待线程。
Linux示例:
Linux 中pthread_mutex实际使用了futex(fast userspace mutex)
futex 运行用户态,快速获取锁,失败时才陷入内核挂起,避免频繁陷入内核态。
二、自旋锁(Spinlock )
原理
不会挂起线程,而是获取不到锁时,持续循环检查锁状态(“忙等待”)
适用于临界区非常短、线程不会被长时间阻塞的场景(如内核中断上下文)
实现方式
使用原子质量(如xchg,cmpxhcg)实现加锁
CPU指令层级提供如LOCK CMPXCHG来保证总线原子性。
缺点
占用CPU资源,等待时间线程无法做其他工作。
多核系统上会导致缓存一直性流量(chache coherence traffice)增加。
三、读写锁(Read-Write Lock)
原理
允许多个读者共享访问,但写者必须独占。
优化了读多写少的场景。
实现机制
通常维护一个计数器记录读者数量。
写线程必须等待所有读者释放锁后才能进入。
内核或者用户态原子操作与条件变量管理状态
操作系统支持
POSIX提供pthread_rwlock ,Linux 内部实现使用类似rw_semaphore.
四、信号量(Semaphore)
原理
计数信号量可控制对资源的访问数量
二值信号量可以作为互斥锁的替代。
实现机制
内核维护一个计数器,P (wait) 操作将其减一,若<0则阻塞;V(signal )操作将其加一,唤醒等待线程
五、实现关键点:从硬件到内核
层级 | 关键点 |
---|---|
CPU | 提供原子操作:CMPXCHG,XCHG,LL/SC (ARM/MIPS) |
缓存一致性 | 确保多核之间对锁变量的访问保持一致(MESI协议) |
内核调度器 | 维护等待队列,挂起线程,唤醒线程 |
系统调用 | 用户态锁失败后,通过futex进入内核挂起 |
六、总结与对比
锁类型 | 是否阻塞线程 | 性能 | 场景 |
---|---|---|---|
互斥锁 | 是 | 较好 | 通用 |
自旋锁 | 否(忙等) | 非常高(短临界区) | 内核、无阻塞上下文 |
读写锁 | 是 | 优于互斥(读多) | 多读少些场景 |
信号量 | 是 | 适中 | 计数共享资源控制 |