作用
在nginx中,此锁用于共享内存分配时上锁,用于进程互斥以及进程内线程互斥。
初始化
ngx_shmtx_create 函数实现
ngx_int_t
ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t *addr, u_char *name)
{
mtx->lock = &addr->lock;
if (mtx->spin == (ngx_uint_t) -1) {
return NGX_OK;
}
mtx->spin = 2048;
#if (NGX_HAVE_POSIX_SEM)
mtx->wait = &addr->wait;
if (sem_init(&mtx->sem, 1, 0) == -1) {
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,
"sem_init() failed");
} else {
mtx->semaphore = 1;
}
#endif
return NGX_OK;
}
mtx->lock 原子操作数,在加锁时,作为判断条件值。
mtx->spin 用于判断mtx->lock的次数,nginx的锁不是盲目的轮询判断或者判断只判断一次,它是选取了一个spin的循环判断次数,超过次数,让出cpu或者等待信号处理。
mtx->wait 我理解的是记录等待个数,但是这个值得作用,不太明白,先记着
mtx->semaphore 标记使用信号量,主要在加锁时sem和ngx_sched_yield机制的选取时,做判断。
加锁操作
ngx_shmtx_lock 函数实现
void
ngx_shmtx_lock(ngx_shmtx_t *mtx)
{
ngx_uint_t i, n;
for ( ;; ) {
if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) {
return;
}
if (ngx_ncpu > 1) {
for (n = 1; n < mtx->spin; n <<= 1) {
for (i = 0; i < n; i++) {
ngx_cpu_pause();
}
if (*mtx->lock == 0
&& ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid))
{
return;
}
}
}
#if (NGX_HAVE_POSIX_SEM)
if (mtx->semaphore) {
(void) ngx_atomic_fetch_add(mtx->wait, 1);
if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) {
(void) ngx_atomic_fetch_add(mtx->wait, -1);
return;
}
while (sem_wait(&mtx->sem) == -1) {
ngx_err_t err;
err = ngx_errno;
if (err != NGX_EINTR) {
break;
}
}
continue;
}
#endif
ngx_sched_yield();
}
}
```c
if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) {
return;
}
使用ngx_pid 对lock赋值,ngx_pid用于区分进程,不可能重复,非常巧妙,不用程序维护。
接下来是一个for循环判断,判断多次无果,才进行下一步休眠操作或等待操作,和nginx的自旋锁一个机制
里面有关于自旋锁的介绍
接下来是一个选择,在自旋锁直接使用ngx_sched_yield函数,让出cpu,等待下次判断,在共享内存锁多了一个
信号量的选择。
if (mtx->semaphore) {
(void) ngx_atomic_fetch_add(mtx->wait, 1);
if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) {
(void) ngx_atomic_fetch_add(mtx->wait, -1);
return;
}
将wait原子加一,然后判断lock的值
while (sem_wait(&mtx->sem) == -1) {
ngx_err_t err;
err = ngx_errno;
if (err != NGX_EINTR) {
break;
}
}
continue;
等待sem_post唤醒,此时阻塞。
sem_wait和ngx_sched_yield的对比,sem_wait是一个等待通知的机制,sched_yield是一个循环遍历的机制,在自旋锁使用sched_yield是因为自旋时间断,快速循环遍历,在共享内存锁中,可能需要等待时间长,使用sem机制,避免cpu浪费,是这样理解吗?
解锁操作
ngx_shmtx_unlock 函数
if (ngx_atomic_cmp_set(mtx->lock, ngx_pid, 0)) {
ngx_shmtx_wakeup(mtx);
}
static void
ngx_shmtx_wakeup(ngx_shmtx_t *mtx)
{
#if (NGX_HAVE_POSIX_SEM)
ngx_atomic_uint_t wait;
if (!mtx->semaphore) {
return;
}
for ( ;; ) {
wait = *mtx->wait;
if ((ngx_atomic_int_t) wait <= 0) {
return;
}
if (ngx_atomic_cmp_set(mtx->wait, wait, wait - 1)) {
break;
}
}
if (sem_post(&mtx->sem) == -1) {
}
#endif
}
ngx_atomic_cmp_set 将lock设置0,执行ngx_shmtx_wakeup。如果使用ngx_sched_yield休眠,ngx_shmtx_wakeup函数无意义,ngx_shmtx_wakeup主要唤醒sem。
强制解锁
这个操作厉害了,解决程序不稳定的福音,多进程操作共享内存锁,如果在锁操作过程崩了,所有进程都凉凉,此时,在进程启动时,这个强制解锁,就是一个解决办法。
ngx_uint_t
ngx_shmtx_force_unlock(ngx_shmtx_t *mtx, ngx_pid_t pid)
{
if (ngx_atomic_cmp_set(mtx->lock, pid, 0)) {
ngx_shmtx_wakeup(mtx);
return 1;
}
return 0;
}
参数是加锁时的进程号。
好嘞,结束!