nginx 锁实现思路

锁从功能上划分主要有互斥锁和读写锁。

不主动释放CPU的锁是spinlock,可以用于竞争较少并且临界区较小的场景,也可以用于中断上下文中。

锁一般用信号量、原子操作(cas)或文件锁来实现。

nginx中实现了互斥锁和读写锁以及spinlock。

互斥锁

加锁:

lock是一个原子变量,0表示没有上锁,ngx_pid表示被对应的进程加锁,如果当前被其它进程加锁,说明其它进程正在执行临界区内的代码,不会马上释放锁,所以会根据spin一会儿再尝试加锁,如果不spin而一直不断尝试访问共享变量将会增加核间通信的负担,ngx_cpu_pause()防止循环代码被优化。

支持posix信号量的情况下,如果spin几次都没有加锁成功则使用信号量进行排队,wait表示排队的个数。

循环调用sem_wait(&mtx->sem) 将进程阻塞等待其它进程释放锁,其它进程释放锁时会检查是否有进程进行wait,如果有则通过信号量wakeup正在wait的一个进程,用信号量避免一直占用CPU。

解锁:

解锁就是将lock赋值为0。然后会调用ngx_shmtx_wakeup(mtx)函数。

wakeup函数检查当前有没有进程在排队,如果有排队的进程,则通过sem_post(&mtx->sem)激活一个排队的进程。

这个for循环是一个小技巧,先将wait共享变量保存在本地局部变量中,然后对wait进行检查,最后再通过cas修改wait共享变量,如果在这中间wait变量被其它进程修改,那么本次cas就是失败,这样就可以保证将wait检查和wait修改两个步骤原子执行,这也是乐观锁的实现。

读写锁

nginx实现的读写锁对写锁不太友好,可能会有写锁饥饿的问题。

#define NGX_RWLOCK_WLOCK ((ngx_atomic_uint_t) -1) 定义写锁宏

加写锁:

没有锁的时候加写锁。

加读锁:

加读锁的时候依然用到了上面的技巧,如果没有加写锁,则加读共享锁。

解锁:

先解写锁,再解读锁。

spinlock

加锁:

spinlock加锁居然也会调用yield,说明这个spinlock目前只能用于进程上下文。

解锁:

#define ngx_unlock(lock) *(lock) = 0

这个地方没有指明内存序会不会有问题呢?我理解其它进程会在本进程解锁的一段时间之后才会发现这次解锁,会不会造成饥饿?待研究。

读写锁(优先写锁)

加写锁:

如果当前无锁,则加写锁,如果当前被上锁,则通过信号量等待。

加读锁:

如果当前不是写锁并且队列中没有等待的写锁,则加读锁,否则自旋加读锁。

解锁:

解写锁,然后weakup等待的写锁。

解读锁,如果读锁全部完成,然后weakup等待的写锁。

weakup函数不变,如果wait不为0,则激活一个写锁。不知道是不是正确啊,哈哈

也看到用两个互斥锁实现读写锁的例子,仅使用互斥锁实现读写锁,有才啊 

你可能感兴趣的:(nginx 锁实现思路)