1. 缘起 nginx
随着SMP(Symmetrical Multi-Processing)架构的流行和epoll类系统调用对非阻塞fd监视的支持,高性能服务器端的开发已经可以实现CPU计算和IO的分离。为了充分发挥CPU的计算能力,服务器端的设计必需要尽可能减小线程切换。引发线程切换最重要的缘由之一就是对mutex和semaphor等锁的使用。本文从计算机体系架构、操做系统的支持和mutex的实现完全分析Linux用户空间mutex的实现,分析的源码版本是glib-2.3.4和kernel-2.6.8。
2.
体系结构和指令的支持
在UP(uni processor)架构下,从用户空间的角度看,中断打断了程序的正常执行。操做系统在处理完中断以后,返回用户空间的以前,从新调度系统中的线程执行。因为CPU是在执行汇编指令结束后响应中断,那么单条汇编指令的执行就是原子的。
在SMP下,因为存在CPU Local Cache和每一个CPU的指令周期不一样,单条汇编指令的执行不会是原子的。X86 SMP提供了一个lock指令前缀,使得某些汇编指令的执行是原子的。看以下x86_64体系结构的汇编代码,来自glibc。
Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2中对cmpxchg指令的解释以下:
This instruction can be used with a LOCK prefix to allow the instruction to be executed atomically.
全部以lock为前缀的指令都起内存栅栏的做用。内存栅栏使编译器确保对RAM中数据的改变对全部CPU都是可见的。
上述汇编对应的伪代码:
Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2中对cmpxchg指令的解释以下:
This instruction can be used with a LOCK prefix to allow the instruction to be executed atomically.
全部以lock为前缀的指令都起内存栅栏的做用。内存栅栏使编译器确保对RAM中数据的改变对全部CPU都是可见的。
上述汇编对应的伪代码:
3.
操做系统支持
实现
wait的步骤以下:
实现
wake up的步骤以下:
A. 执行
wait的
A到
C。
C. 唤醒在锁上的一个等待线程。
4.
pthread_mutex
实现分析
pthread_mutex_lock()实如今
glibc-2.3.4 pthread_mutex_lock.c文件的
33行,该函数会根据
mutex在
init的时候设置的属性,选择不一样的执行路径。
mutex的属性有四种:
A.
PTHREAD_MUTEX_TIMED_NP:默认属性。
pthread_mutex_lock()直接调用
lll_mutex_lock()。
B.
PTHREAD_MUTEX_RECURSIVE_NP:检查
mutex owner 是否为当前线程。该属性容许线程屡次获取该锁。
C.
PTHREAD_MUTEX_ERRORCHECK_NP:若是同一线程两次
lock,会返回错误。
D.
PTHREAD_MUTEX_ADAPTIVE_NP:该锁会先
n次调用
lll_mutex_trylock(),
n为用户定义和
100的最小值。若是仍然失败,则调用
lll_mutex_lock()。
lll_mutex_trylock()不会调用
futex。
5.
spin lock
实现
nginx实现了
spin lock以保护多进程对
listen port的互斥
accept。
spinlock的实现以下:
Spinlock本质上是一个“忙等”锁,因为其不存在下节中总结的
mutex的缺点,其对于小资源是最高效的锁。相比上节中
mutex的
PTHREAD_MUTEX_ADAPTIVE_NP属性,
nginx的
spinlock是一个更完美的实现方案。
6.
总结
在设置
PTHREAD_MUTEX_TIMED_NP属性和单进程多线程模型下,
pthread_mutex_lock()对同进程的其余线程的影响以下:
A.
pthread_mutex_lock()占用的大部分
CPU时间当中,直接影响其余线程调用
mmap(),
brk(),
malloc和
free()。
B. 对进程处理
page fault也会有影响。
D. 最重要的是,锁的使用会引发线程的频繁切换,致使
cpu cache miss和
TLB miss。
对于系统中,须要互斥访问的资源,以下建议:
A. 内核中对于小资源如链表的增删,可能是使用
spin lock保护。
B. 在设置
PTHREAD_MUTEX_ADAPTIVE_NP属性下,
mutex既能够是
spin lock,也能够是阻塞锁。
C. 使用
atomic_add_return(i, v),原子对变量
i增长
v值,而且返回操做后的值。相反操做:
atomic_sub_return(i, v)。
D. 使用
Per-CPU variables,例如多线程程序中要每隔
1秒,统计某项操做的值。该变量最好是
cache alignment。
E. 对于如数据库频繁更新的操做,可使用数据库的多版本并发控制方法减小对
mutex的
lock。