问题引入
多线程或者多进程程序访问同一个变量时,需要加锁才能实现变量的互斥访问,否则结果可能是无法预期的,即存在并发问题。解决并发问题通常有两种方案:
1)加锁:访问变量之前加锁,只有加锁成功才能访问变量,访问变量之后需要释放锁;这种通常称为悲观锁,即认为每次变量访问都会导致并发问题,因此每次访问变量之前都加锁。
2)原子操作:只要访问变量的操作是原子的,就不会导致并发问题。那表达式么i++是不是原子操作呢?
nginx通常会有多个worker处理请求,多个worker之间需要通过抢锁的方式来实现监听事件的互斥处理,由函数ngx_shmtx_trylock实现抢锁逻辑,代码如下:
ngx_uint_t ngx_shmtx_trylock(ngx_shmtx_t *mtx)
{
return (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid));
}
变量mtx->lock指向的是一块共享内存地址(所有worker都可以访问);worker进程会尝试设置变量mtx->lock的值为当前进程号,如果设置成功,则说明抢锁成功,否则认为抢锁失败。
注意ngx_atomic_cmp_set设置变量mtx->lock的值为当前进程号并不是无任何条件的,而是只有当变量mtx->lock值为0时才设置,否则不予设置。ngx_atomic_cmp_set是典型的比较-交换操作,且必须加锁或者是原子操作才行,函数实现方式下节分析。
nginx有一些全局统计变量,比如说变量ngx_connection_counter,此类变量由所有worker进程共享,并发执行累加操作,由函数ngx_atomic_fetch_add实现;而该累加操作需要加锁或者时原子操作才行,函数实现方式下节分析。
上面说的mtx->lock和ngx_connection_counter都是共享变量,所有worker进程都可以访问,这些变量在ngx_event_core_module模块的ngx_event_module_init函数创建,且该函数在fork worker进程之前执行。
/* cl should be equal to or greater than cache line size */
cl = 128;
size = cl /* ngx_accept_mutex */
+ cl /*ngx_connection_counter */
+ cl;