32

我将flock()用于名为互斥体的进程间(即某些进程可以决定对“some_name”进行锁定,这是通过在临时目录中锁定一个名为“some_name”的文件来实现的:

lockfile = "/tmp/some_name.lock";
fd = open(lockfile, O_CREAT);
flock(fd, LOCK_EX);

do_something();

unlink(lockfile);
flock(fd, LOCK_UN);

应该在某个时候删除锁定文件,以避免用数百个文件填充临时目录。

但是,这段代码中有一个明显的竞争条件;以进程 A、B 和 C 为例:

A opens file
A locks file
B opens file
A unlinks file
A unlocks file
B locks file (B holds a lock on the deleted file)
C opens file (a new file one is created)
C locks file (two processes hold the same named mutex !)

有没有办法在不引入这种竞争条件的情况下在某个时候删除锁定文件?

4

3 回答 3

36

对不起,如果我回答一个死问题:

锁定文件后,打开它的另一个副本,fstat 两个副本并检查 inode 号,如下所示:

lockfile = "/tmp/some_name.lock";

    while(1) {
        fd = open(lockfile, O_CREAT);
        flock(fd, LOCK_EX);

        fstat(fd, &st0);
        stat(lockfile, &st1);
        if(st0.st_ino == st1.st_ino) break;

        close(fd);
    }

    do_something();

    unlink(lockfile);
    flock(fd, LOCK_UN);

这可以防止竞争条件,因为如果一个程序持有一个仍在文件系统上的文件的锁,那么所有其他有剩余文件的程序都会有一个错误的 inode 号。

我实际上在状态机模型中证明了它,使用以下属性:

如果 P_i 有一个锁定在文件系统上的描述符,则没有其他进程位于临界区。

如果 P_i 在具有正确 inode 的 stat 之后或在关键部分中,则它的描述符已锁定在文件系统上。

于 2013-09-11T15:25:55.230 回答
8
  1. 在 Unix 中,可以在打开文件时删除文件 - inode 将一直保留,直到所有在文件描述符列表中包含它的进程结束
  2. 在 Unix 中,可以通过检查链接计数变为零来检查文件是否已从所有目录中删除

因此,您可以简单地检查已打开文件的 nlink 计数,而不是比较旧/新文件路径的 ino 值。它假定它只是一个临时锁定文件,而不是真正的互斥锁资源或设备。

lockfile = "/tmp/some_name.lock";

for(int attempt; attempt < timeout; ++attempt) {
    int fd = open(lockfile, O_CREAT, 0444);
    int done = flock(fd, LOCK_EX | LOCK_NB);
    if (done != 0) { 
        close(fd);
        sleep(1);     // lock held by another proc
        continue;
    }
    struct stat st0;
    fstat(fd, &st0);
    if(st0.st_nlink == 0) {
       close(fd);     // lockfile deleted, create a new one
       continue;
    }
    do_something();
    unlink(lockfile); // nlink :=0 before releasing the lock
    flock(fd, LOCK_UN);
    close(fd);        // release the ino if no other proc 
    return true;
}
return false;
于 2018-06-27T20:18:46.083 回答
4

如果您仅将这些文件用于锁定,而不实际写入它们,那么我建议您将目录条目本身的存在视为持有锁的指示,并避免flock完全使用。

为此,您需要构建一个操作,该操作创建一个目录条目并在它已经存在时报告错误。在 Linux 和大多数文件系统上,传递O_EXCLopen将为此工作。但是某些平台和某些文件系统(尤其是较旧的 NFS)不支持此功能。因此,手册页open提出了另一种选择:

想要使用 lockfile 执行原子文件锁定并且需要避免依赖 NFS 支持的便携式程序O_EXCL可以在同一文件系统上创建唯一文件(例如,合并主机名和 PID),并使用link(2) 生成链接到锁定文件。如果link(2)返回0,则锁定成功。否则,stat对唯一文件使用 (2) 来检查其链接计数是否已增加到 2,在这种情况下,锁定也是成功的。

所以这看起来像是一个正式记录的锁定方案,因此表明了一定程度的支持和最佳实践建议。但我也看到了其他方法。例如bzr在大多数地方使用目录而不是符号链接。引用其源代码

锁在磁盘上由具有特定名称的目录表示,其中包含一个信息文件。通过将临时目录重命名到位来完成锁定。我们使用临时目录,因为对于所有已知的传输和文件系统,我们相信只有一次申请锁的尝试会成功,而其他的会失败。(文件不会这样做,因为某些文件系统或传输只有重命名和覆盖,因此很难判断谁赢了。)

上述方法的一个缺点是它们不会阻塞:失败的锁定尝试将导致错误,但不会等到锁定可用。您将不得不轮询锁,这可能会因为锁争用而产生问题。在这种情况下,您可能希望进一步脱离基于文件系统的方法,转而使用第三方实现。但是关于如何做ipc互斥的一般问题已经被问到了,所以我建议你搜索[ipc] [mutex]并查看结果,特别是这个。顺便说一句,这些标签也可能对您的帖子有用。

于 2013-07-18T04:55:51.510 回答