1 引言
当涉及到高并发的 Web 服务器,如本文将讨论的nginx,正确处理并发访问是至关重要的。在这种情况下,互斥锁(Mutex)和读写锁(ReadWrite Lock)成为保证共享资源安全访问的关键机制。本文将深入探讨 nginx 中互斥锁和读写锁的实现逻辑,帮助读者理解这些机制的工作原理以及如何在自己的代码中应用。通过正确使用互斥锁和读写锁,我们可以保护共享资源免受竞态条件和数据不一致性等问题的影响,从而提高服务器的性能和可靠性。
我们一般认为nginx是一个多进程单线程的应用服务,虽然nginx在一个worker进程内是没有数据竞争问题的(因为是单线程),但是不免nginx在多个进程间还有一些需要共享的数据,譬如ngx_http_upstream_zone_module模块将peers数据放在了共享内存中供多个worker进程来使用,又譬如ngx_http_limit_conn_module模块将并发连接数限制也放在了共享内存中,诸如此类的,自然会涉及到共享内存访问的互斥锁的问题。
本文从源码层面对nginx的互斥锁和读写锁的实现进行分析,通过分析学习nginx的实现代码,以便将来可以应用到自己的日常应用程序中去。
2 互斥锁(Mutex)
nginx的互斥锁是一种独占锁,它用于保护临界区资源,确保同一时间只有一个线程可以访问该资源。nginx 中的互斥锁是通过原子操作和底层操作系统提供的互斥机制实现的。
当一个线程需要访问被互斥锁保护的资源时,它首先尝试获取锁。如果锁已经被其他线程获取,则该线程会被阻塞,直到锁被释放。一旦线程成功获取到锁,它就可以执行临界区代码。当线程完成对共享资源的访问后,它释放锁,使得其他线程可以获取锁并继续执行。
nginx 使用互斥锁的场景包括访问共享内存、更新全局变量和数据结构等。
nginx根据底层操作系统的特性进行了两个版本的实现,包括采用文件锁的机制和原子锁两种方式。
2.1 文件锁版本的互斥锁:
文件锁版本的通用性比较高,几乎所有的操作系统都能够支持,但是性能肯定相对比较差一些,但是好处是实现简单,通用型强,源码如下:
void
ngx_shmtx_lock(ngx_shmtx_t *mtx)
{
ngx_err_t err;
err = ngx_lock_fd(mtx->fd);
if (err == 0) {
return;
}
ngx_log_abort(err, ngx_lock_fd_n " %s failed", mtx->name);
}
void
ngx_shmtx_unlock(ngx_shmtx_t *mtx)
{
ngx_err_t err;
err = ngx_unlock_fd(mtx->fd);
if (err == 0) {
return;
}
ngx_log_abort(err, ngx_unlock_fd_n " %s failed", mtx->name);
}
其中ngx_lock_fd就是调用fcntl进行文件加锁操作,ngx_unlock_fd同样是调用fcntl进行文件解锁操作。源码如下:
ngx_err_t
ngx_lock_fd(ngx_fd_t fd)
{
struct flock fl;
ngx_memzero(&fl, sizeof(struct flock));
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
if (fcntl(fd, F_SETLKW, &fl) == -1) {
return ngx_errno;
}
return 0;
}
ngx_err_t
ngx_unlock_fd(ngx_fd_t fd)
{