14.6 信号量

信号量与信号不同,它不像信号那样用来传递消息,而是用来保证两个或多个代码段不被并发访问,是一种保证共享资源有序访问的工具。使用信号量作为互斥锁有可能导致进程睡眠,因此,要谨慎使用,特别是对于Nginx这种每一个进程同时处理着数以万计请求的服务器来说,这种导致睡眠的操作将有可能造成性能大幅降低。

信号量提供的用法非常多,但Nginx仅把它作为简单的互斥锁来使用,下面只会介绍这种用法。定义一个sem_t类型的变量后,即可围绕着它使用信号量。使用前,先要调用sem_init方法初始化信号量,如下所示。


int sem_init(sem_t*sem,int pshared,unsigned int value);


其中,参数sem即为我们定义的信号量,而参数pshared将指明sem信号量是用于进程间同步还是用于线程间同步,当pshared为0时表示线程间同步,而pshared为1时表示进程间同步。由于Nginx的每个进程都是单线程的,因此将参数pshared设为1即可。参数value表示信号量sem的初始值。下面看看在ngx_shmtx_create方法中是如何初始化信号量的。


ngx_int_t ngx_shmtx_create(ngx_shmtx_tmtx,voidaddr,u_char*name)

{

……

if(NGX_HAVE_POSIX_SEM)

//信号量mtx->sem初始化为0,用于进程间通信

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;

}


ngx_shmtx_t结构体将会在14.8节中介绍。可以看到,在定义了NGX_HAVE_POSIX_SEM宏后,将开始使用信号量。另外,sem_destroy方法可以销毁信号量。例如:


void ngx_shmtx_destory(ngx_shmtx_t*mtx)

{

if(NGX_HAVE_POSIX_SEM)

if(mtx->semaphore){

if(sem_destroy(&mtx->sem)==-1){

ngx_log_error(NGX_LOG_ALERT,ngx_cycle->log,ngx_errno,"sem_destroy()

failed");

}

}

endif

}


信号量是如何实现互斥锁功能的呢?例如,最初的信号量sem值为0,调用sem_post方法将会把sem值加1,这个操作不会有任何阻塞;调用sem_wait方法将会把信号量sem的值减1,如果sem值已经小于或等于0了,则阻塞住当前进程(进程会进入睡眠状态),直到其他进程将信号量sem的值改变为正数后,这时才能继续通过将sem减1而使得当前进程继续向下执行。因此,sem_post方法可以实现解锁的功能,而sem_wait方法可以实现加锁的功能。

例如,ngx_shmtx_lock方法在加锁时,有可能到使用sem_wait的分支去试图获得锁,如下所示。


void ngx_shmtx_lock(ngx_shmtx_t*mtx)

{

……

//如果没有拿到锁,这时Nginx进程将会睡眠,直到其他进程释放了锁

while(sem_wait(&mtx->sem)==-1){

}

……

}


ngx_shmtx_lock方法会在14.8节详细说明。ngx_shmtx_unlock方法在释放锁时也会用到sem_post方法,如下所示。


void ngx_shmtx_unlock(ngx_shmtx_t*mtx)

{

……

//释放信号量锁时是不会使进程睡眠的

if(sem_post(&mtx->sem)==-1){

ngx_log_error(NGX_LOG_ALERT,ngx_cycle->log,ngx_errno,

"sem_post()failed while wake shmtx");

}

……

}


在14.8节中我们将会讨论Nginx是如何让原子变量和信号量合作以实现高效互斥锁的。