一、数据结构
typedef struct {
#if (NGX_HAVE_ATOMIC_OPS)
ngx_atomic_t *lock;
#if (NGX_HAVE_POSIX_SEM)
ngx_atomic_t *wait;
ngx_uint_t semaphore;
sem_t sem;
#endif
#else
ngx_fd_t fd;
u_char *name;
#endif
ngx_uint_t spin;
} ngx_shmtx_t;
二、三种实现
- 不支持原子操作时,使用文件锁来实现ngx_shmtx_t互斥锁;
- 支持原子操作,但不支持信号量;(一段时间获取不到锁会自旋)
- 支持原子操作,也支持信号量。(一段时间获取不到锁会睡眠)
遗留:ngx_accept_mutex的sem_t并没有放在共享内存里,是如何跨进程的呢?
三、代码实现
锁创建
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;
}
当spin为-1时,表示不使能信号量,即进程不会进入睡眠。当需要nginx支持数以万计的并发TCP请求时,通常都会把spin设为-1,这时互斥锁在取琐时会采用自旋的方式。
当spin不为-1时,会将信号量初始化为0。
trylock
ngx_uint_t
ngx_shmtx_trylock(ngx_shmtx_t *mtx)
{
return (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid));
}
当原子变量lock不为0,表示锁未被占用,则将lock赋值为ngx_pid,lock不为0表示锁被占用。
### lock
void
ngx_shmtx_lock(ngx_shmtx_t *mtx)
{
ngx_uint_t i, n;
ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "shmtx lock");
for ( ;; ) {
/*1. 尝试获取锁,将lock赋值为ngx_pid表示占有锁*/
if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) {
return;
}
/*2. 自旋阶段持续尝试获取锁*/
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)
/*3. 若使能信号量,则睡眠等待sem_post触发*/
if (mtx->semaphore) {
/*3.1 增加锁等待的引用计数,wait的值即为睡眠的进程数*/
(void) ngx_atomic_fetch_add(mtx->wait, 1);
/*3.2 尝试获取锁*/
if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) {
(void) ngx_atomic_fetch_add(mtx->wait, -1);
return;
}
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
"shmtx wait %uA", *mtx->wait);
/*3.3 睡眠等待其他进程释放锁*/
while (sem_wait(&mtx->sem) == -1) {
ngx_err_t err;
err = ngx_errno;
if (err != NGX_EINTR) {
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, err,
"sem_wait() failed while waiting on shmtx");
break;
}
}
ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
"shmtx awoke");
continue;
}
#endif
/*4. 不使用信号量时,调用ngx_sched_yield暂时让出处理器*/
ngx_sched_yield();
}
}
unlock
将lock置为0,将锁释放。
ngx_atomic_cmp_set(mtx->lock, ngx_pid, 0);
唤醒等待进程
static void
ngx_shmtx_wakeup(ngx_shmtx_t *mtx)
{
#if (NGX_HAVE_POSIX_SEM)
ngx_atomic_uint_t wait;
/*1. 不使能信号量情况下,支持退出*/
if (!mtx->semaphore) {
return;
}
/*2. 减少wait引用计数*/
for ( ;; ) {
wait = *mtx->wait;
if ((ngx_atomic_int_t) wait <= 0) {
return;
}
if (ngx_atomic_cmp_set(mtx->wait, wait, wait - 1)) {
break;
}
}
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
"shmtx wake %uA", wait);
/*3. 调用sem_post使阻塞进程被唤醒*/
if (sem_post(&mtx->sem) == -1) {
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,
"sem_post() failed while wake shmtx");
}
#endif
}