一、原理
nginx的锁是基于共享内存实现的,这点跟redis中利用一个存储(也就是一个键值对)来实现锁的原理是一致的,每一项操作通过检查锁对象的lock域是否为0,来判断能否获取锁并尝试获取锁。
二、锁的类定义
1. 类定义
//锁的定义
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
2. 框架初始化时机
ngx_event_core_module中调用init函数来初始化这段锁的共享内存
/*后面将会创建size大小的共享内存,这块共享内存将被均分成三段,
分别供ngx_accept_mutex、ngx_connection_counter、
ngx_temp_number 使用。*/
/* cl should be equal to or greater than cache line size */
cl = ;
size = cl /* ngx_accept_mutex */
+ cl /* ngx_connection_counter */
+ cl; /* ngx_temp_number */ //共享内存的初始化
shm.size = size;
shm.name.len = sizeof("nginx_shared_zone");
shm.name.data = (u_char *) "nginx_shared_zone";
shm.log = cycle->log; if (ngx_shm_alloc(&shm) != NGX_OK) { //为共享内存分配内存空间
return NGX_ERROR;
} shared = shm.addr; //获取共享内存的地址 ngx_accept_mutex_ptr = (ngx_atomic_t *) shared; //存放互斥量内存地址的指针
ngx_accept_mutex.spin = (ngx_uint_t) -; //初始化自旋锁的初值为-1 if (ngx_shmtx_create(&ngx_accept_mutex, (ngx_shmtx_sh_t *) shared, //如果支持原子操作的话,这个就很简单了,就直接将内存地址分配过去就行了
cycle->lock_file.data)
!= NGX_OK)
{
return NGX_ERROR;
} ngx_connection_counter = (ngx_atomic_t *) (shared + * cl); //ngx_connection_counter为其分配共享内存的内存空间 (void) ngx_atomic_cmp_set(ngx_connection_counter, , ); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, ,
"counter: %p, %d",
ngx_connection_counter, *ngx_connection_counter); ngx_temp_number = (ngx_atomic_t *) (shared + * cl); //ngx_temp_number的内存空间
init
三、基本操作
1. try_lock
lock域是否为0 -> 尝试获取锁 (获取锁的表现就是在lock存入了该进程的pid)-> 返回结果
//尝试获取锁,原子的方式
ngx_uint_t
ngx_shmtx_trylock(ngx_shmtx_t *mtx)
{
return (*mtx->lock == && ngx_atomic_cmp_set(mtx->lock, , ngx_pid));
}
try_lock
2. lock
循环获取锁
//阻塞的方式获取锁
void
ngx_shmtx_lock(ngx_shmtx_t *mtx)
{
ngx_uint_t i, n; ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, , "shmtx lock");
//一个死循环,不断的去看是否获取了锁,直到获取了之后才退出
for ( ;; ) {
//如果获取了锁,那么就可以直接返回了
if (*mtx->lock == && ngx_atomic_cmp_set(mtx->lock, , ngx_pid)) {
return;
}
//如果cpu的数量大于一
if (ngx_ncpu > ) {
for (n = ; n < mtx->spin; n <<= ) {
for (i = ; i < n; i++) {
ngx_cpu_pause();
} if (*mtx->lock ==
&& ngx_atomic_cmp_set(mtx->lock, , ngx_pid))
{
return;
}
}
} ngx_sched_yield();
}
}
lock
3.unlock
是否拥有锁 -> 释放锁
//释放锁
void
ngx_shmtx_unlock(ngx_shmtx_t *mtx)
{
if (mtx->spin != (ngx_uint_t) -) {
ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, , "shmtx unlock");
} if (ngx_atomic_cmp_set(mtx->lock, ngx_pid, )) {
ngx_shmtx_wakeup(mtx);
}
}
unlock
四、用锁解决子进程的惊群现象
尝试获取锁 -> 成功 -> 已持有已把监听端口添加到epoll,返回NGX_OK,未添加则添加到epoll中并设置为持有
尝试获取锁 -> 失败 -> 若以前获取过,需要取消监听
//尝试获取锁,如果获取了锁,那么还要将当前监听端口全部注册到当前worker进程的epoll当中去
ngx_int_t
ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
{
if (ngx_shmtx_trylock(&ngx_accept_mutex)) { //尝试获取互斥锁 ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, ,
"accept mutex locked");
//如果本来已经获得锁,则直接返回Ok
if (ngx_accept_mutex_held
&& ngx_accept_events ==
&& !(ngx_event_flags & NGX_USE_RTSIG_EVENT))
{
return NGX_OK;
}
//到达这里,说明重新获得锁成功,因此需要打开被关闭的listening句柄,调用ngx_enable_accept_events函数,将监听端口注册到当前worker进程的epoll当中去
if (ngx_enable_accept_events(cycle) == NGX_ERROR) {
ngx_shmtx_unlock(&ngx_accept_mutex);
return NGX_ERROR;
} ngx_accept_events = ;
ngx_accept_mutex_held = ; //表示当前获取了锁 return NGX_OK;
} ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, ,
"accept mutex lock failed: %ui", ngx_accept_mutex_held);
//这里表示的是以前曾经获取过,但是这次却获取失败了,那么需要将监听端口从当前的worker进程的epoll当中移除,调用的是ngx_disable_accept_events函数
if (ngx_accept_mutex_held) {
if (ngx_disable_accept_events(cycle) == NGX_ERROR) {
return NGX_ERROR;
} ngx_accept_mutex_held = ; //表示当前并没有获取锁
} return NGX_OK;
}
ngx_trylock_accept_mutex
参考文献:
https://www.cnblogs.com/549294286/p/6058811.html