mxyx

nginx的锁

一、原理

  nginx的锁是基于共享内存实现的,这点跟redis中利用一个存储(也就是一个键值对)来实现锁的原理是一致的,每一项操作通过检查锁对象的lock域是否为0,来判断能否获取锁并尝试获取锁。 

 

二、锁的类定义

1. 类定义

 1 //锁的定义
 2 typedef struct {
 3 #if (NGX_HAVE_ATOMIC_OPS)
 4     ngx_atomic_t  *lock;  //如果支持原子锁的话,那么使用它
 5 #if (NGX_HAVE_POSIX_SEM) 
 6     ngx_atomic_t  *wait;
 7     ngx_uint_t     semaphore;
 8     sem_t          sem;
 9 #endif
10 #else
11     ngx_fd_t       fd;   //不支持原子操作的话就使用文件锁来实现
12     u_char        *name;
13 #endif
14     ngx_uint_t     spin;     //这是自旋锁么?
15 } ngx_shmtx_t;
ngx_shmtx_t

 

2. 框架初始化时机

  ngx_event_core_module中调用init函数来初始化这段锁的共享内存 

 1 /*后面将会创建size大小的共享内存,这块共享内存将被均分成三段, 
 2 分别供ngx_accept_mutex、ngx_connection_counter、
 3 ngx_temp_number 使用。*/  
 4     /* cl should be equal to or greater than cache line size */
 5     cl = 128;
 6     size = cl            /* ngx_accept_mutex */
 7            + cl          /* ngx_connection_counter */
 8            + cl;         /* ngx_temp_number */
 9 
10     //共享内存的初始化
11     shm.size = size;
12     shm.name.len = sizeof("nginx_shared_zone");
13     shm.name.data = (u_char *) "nginx_shared_zone";
14     shm.log = cycle->log;
15 
16     if (ngx_shm_alloc(&shm) != NGX_OK) {   //为共享内存分配内存空间
17         return NGX_ERROR;
18     }
19 
20     shared = shm.addr;   //获取共享内存的地址
21 
22     ngx_accept_mutex_ptr = (ngx_atomic_t *) shared;   //存放互斥量内存地址的指针
23     ngx_accept_mutex.spin = (ngx_uint_t) -1;   //初始化自旋锁的初值为-1
24 
25     if (ngx_shmtx_create(&ngx_accept_mutex, (ngx_shmtx_sh_t *) shared,  //如果支持原子操作的话,这个就很简单了,就直接将内存地址分配过去就行了
26        cycle->lock_file.data)
27         != NGX_OK)
28     {
29         return NGX_ERROR;
30     }
31 
32     ngx_connection_counter = (ngx_atomic_t *) (shared + 1 * cl);           //ngx_connection_counter为其分配共享内存的内存空间
33 
34     (void) ngx_atomic_cmp_set(ngx_connection_counter, 0, 1);
35 
36     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
37                    "counter: %p, %d",
38                    ngx_connection_counter,     *ngx_connection_counter);
39 
40     ngx_temp_number = (ngx_atomic_t *) (shared + 2 * cl);  //ngx_temp_number的内存空间
init

 

三、基本操作

1. try_lock

  lock域是否为0 -> 尝试获取锁 (获取锁的表现就是在lock存入了该进程的pid)-> 返回结果 

1 //尝试获取锁,原子的方式
2 ngx_uint_t
3 ngx_shmtx_trylock(ngx_shmtx_t *mtx)
4 {
5     return (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0,     ngx_pid));
6 }
try_lock

 

2. lock

  循环获取锁

 1 //阻塞的方式获取锁
 2 void
 3 ngx_shmtx_lock(ngx_shmtx_t *mtx)
 4 {
 5     ngx_uint_t         i, n;
 6 
 7     ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "shmtx lock");
 8 //一个死循环,不断的去看是否获取了锁,直到获取了之后才退出
 9     for ( ;; ) {
10 //如果获取了锁,那么就可以直接返回了
11         if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) {
12             return;
13         }
14 //如果cpu的数量大于一
15         if (ngx_ncpu > 1) {
16             for (n = 1; n < mtx->spin; n <<= 1) {
17                 for (i = 0; i < n; i++) {
18                     ngx_cpu_pause();
19                 }
20 
21                 if (*mtx->lock == 0
22                     && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid))
23                 {
24                     return;
25                 }
26             }
27         }
28 
29         ngx_sched_yield();
30     }
31 }
lock

 

3.unlock

  是否拥有锁 -> 释放锁 

 1 //释放锁
 2 void
 3 ngx_shmtx_unlock(ngx_shmtx_t *mtx)
 4 {
 5     if (mtx->spin != (ngx_uint_t) -1) {
 6         ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "shmtx unlock");
 7     }
 8 
 9     if (ngx_atomic_cmp_set(mtx->lock, ngx_pid, 0)) {
10         ngx_shmtx_wakeup(mtx);
11     }
12 }
unlock

 

四、用锁解决子进程的惊群现象

  尝试获取锁 -> 成功 -> 已持有已把监听端口添加到epoll,返回NGX_OK,未添加则添加到epoll中并设置为持有

  尝试获取锁 -> 失败 -> 若以前获取过,需要取消监听 

 1 //尝试获取锁,如果获取了锁,那么还要将当前监听端口全部注册到当前worker进程的epoll当中去
 2 ngx_int_t
 3 ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
 4 {
 5     if (ngx_shmtx_trylock(&ngx_accept_mutex)) {  //尝试获取互斥锁
 6         
 7         ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
 8                        "accept mutex locked");
 9         //如果本来已经获得锁,则直接返回Ok
10         if (ngx_accept_mutex_held
11             && ngx_accept_events == 0
12             && !(ngx_event_flags & NGX_USE_RTSIG_EVENT))
13         {
14             return NGX_OK;
15         }
16         //到达这里,说明重新获得锁成功,因此需要打开被关闭的listening句柄,调用ngx_enable_accept_events函数,将监听端口注册到当前worker进程的epoll当中去
17         if (ngx_enable_accept_events(cycle) == NGX_ERROR) {
18             ngx_shmtx_unlock(&ngx_accept_mutex);
19             return NGX_ERROR;
20         }
21 
22         ngx_accept_events = 0;
23         ngx_accept_mutex_held = 1;  //表示当前获取了锁
24 
25         return NGX_OK;
26     }
27 
28     ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
29                    "accept mutex lock failed: %ui", ngx_accept_mutex_held);
30 //这里表示的是以前曾经获取过,但是这次却获取失败了,那么需要将监听端口从当前的worker进程的epoll当中移除,调用的是ngx_disable_accept_events函数
31     if (ngx_accept_mutex_held) {
32         if (ngx_disable_accept_events(cycle) == NGX_ERROR) {
33             return NGX_ERROR;
34         }
35 
36         ngx_accept_mutex_held = 0;   //表示当前并没有获取锁
37     }
38 
39     return NGX_OK;
40 }
ngx_trylock_accept_mutex

 

参考文献:

https://www.cnblogs.com/549294286/p/6058811.html

posted on 2018-01-19 11:31  mxyx  阅读(412)  评论(0编辑  收藏  举报

导航