Linux 多进程锁的几种实现方案

我们知道,多线程可以用多线程互斥量pthread_mutex_t实现线程之间上锁,那么多进程之间如何共享锁呢?

1. 文件锁实现多进程锁

由于文件锁是存放到位于内存的系统文件表中, 所有进程/线程可通过系统访问。如果不同进程使用同一文件锁(写锁/排他锁),当取得文件锁时,进程可继续执行;如果没有取得锁,则阻塞等待。而唯一标识该文件的是文件路径,因此,可以通过一个共同的文件路径,来实现多进程锁机制。
参见文件锁的本质核心和原理 | CSDN

关键点:
1)创建/打开一个唯一路径的文件;
2)P操作,取得文件的写锁(排他锁);V操作,释放文件的锁;

static struct flock lock_it; /* 用于加锁的flock对象 */
static struct unlock_it;     /* 用于解锁的flock对象 */
static int lock_fd = -1; 

void
my_lock_init(char *pathname)
{
    char lock_file[1024]; /* 存放文件名的临时缓存 */
    
    /* 由于mkstemp会修改参数的最后6个字符,为避免pathname是字符串常量,需要将字符串从pathname copy到临时缓存lock_file中,然后作为创建唯一临时文件的参数 */
    strncpy(lock_file, pathname, sizeof(lock_file));
    lock_fd = mkstemp(lock_file); /* 创建唯一的临时文件并打开 */
    
    unlink(lock_file); /* 删除指定文件名的文件,如果有进程打开了该文件,会在进程结束后所有指向该文件的描述符都关闭后删除文件。这样可以确保即使程序崩溃,临时文件也会完全消失 */

    /* 设置flock对象的写锁属性 */
    lock_it.l_type = F_WRLCK;
    lock_it.l_whence = SEEK_SET;
    lock_it.l_start = 0;
    lock_it.l_len = 0;

    /* 设置flock对象的解锁属性 */
    unlock_it.l_type = F_UNLCK;
    unlock_it.l_whence = SEEK_SET;
    unlock_it.l_start = 0;
    unlock_it.l_len = 0;
}

void
my_lock_wait()
{
    int rc;
    
    /* 取得写锁,如果阻塞等待时被中断唤醒,则继续恢复阻塞等待锁资源 */
    whilel ((rc = fcntl(lock_fd, F_SETLKW, &lock_it)) < 0) {
        if (errno == EINTR) continue;
        else err_sys("my_lock_wait fcntl error");  /* 如果没有调用my_lock_init就直接调用fcntl,将会失败 */
    }
}

void 
my_lock_release()
{
    if (fcntl(lock_fd, F_SETLKW, &unlock_it) < 0)
        err_sys("my_lock_release fcntl error");
}

2. 多线程锁实现多进程锁

多线程之间天然共享内存/变量,而多进程各有自己的进程空间,它们之间是不共享数据的

2个关键步骤
1)互斥锁变量存放到共享内存;
2)设置互斥锁变量的进程共享属性(PTHREAD_PROCESS_SHARED);

static pthread_mutex_t *mptr; /* 互斥锁变量指针,互斥锁变量存放到共享内存 */

/**
* 多进程互斥锁变量初始化
*/
void
my_lock_init()
{
    int fd;
    pthread_mutexattr_t mattr;

    /* 新建共享内存区域,但不映射到实际的普通文件 */
    fd = open("/dev/zero", O_RDWR, 0);
    if (fd < 0) err_sys("open error");
    mptr = mmap(0, sizeof(pthread_mutex_t), PORT_READ | PORT_WRITE, MAP_SHARED, fd, 0);
    if (mptr == MAP_FAILED) err_sys("mmap");
    if (close(fd)) err_sys("close error");
    
    pthread_mutexattr_init(&mattr);
    pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
    phtread_mutex_init(mptr, &mattr);
}

/**
* 加锁
*/
void
my_lock_wait()
{
  pthread_mutex_lock(mptr);
}

/**
* 解锁
*/
void 
my_lock_release()
{
    pthread_mutex_unlock(mptr);
}

对于符合4.4BSD的Linux系统,直接支持匿名内存映射,可以通过指定mmap的参数flag = MAP_ANON,fd = -1,offset被忽略.
而上面将fd指向打开的/dev/zero,是更通用的做法,任何类Unix系统通常都支持。

#ifdef MAP_ANON
ptr = mmap(0, nchildren * sizeof(long), PORT_READ | PORT_WRITE, MAP_ANON | MAP_SHARED, -1 , 0);
#else
int fd;

fd = open("/dev/zero", O_RDWR, 0);
if (fd < 0)
    err_sys("open error");

ptr = mmap(0, nchildren * sizeof(long), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
#endif
posted @ 2021-06-23 18:03  明明1109  阅读(7632)  评论(0编辑  收藏  举报