flock():删除没有竞争条件的锁定文件?

时间:2013-07-17 19:46:25

标签: c flock

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

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

do_something();

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

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

但是,此代码中存在明显的竞争条件;进程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 !)

有没有办法在某些时候删除锁定文件而不引入这种竞争条件?

3 个答案:

答案 0 :(得分:23)

很抱歉,如果我回答一个死的问题:

锁定文件后,打开它的另一个副本,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之后,则它将描述符锁定在文件系统上。

答案 1 :(得分:3)

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

为此,您需要构建一个创建目录条目的操作,并报告错误(如果已存在)。在Linux和大多数文件系统上,将O_EXCL传递给open将适用于此。但是某些平台和某些文件系统(特别是较旧的NFS)不支持此功能。因此man page for open提出了另一种选择:

  

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

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

  

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

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

答案 2 :(得分:2)

  1. 在Unix中,可以在打开文件时将其删除-直到所有进程都结束了ino为止,该ino将保留在文件描述符列表中
  2. 在Unix中,可以通过检查链接计数为零来检查文件是否已从所有目录中删除

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

lockfile = "/tmp/some_name.lock";

for(int attempt; attempt < timeout; ++attempt) {
    int fd = open(lockfile, O_CREAT);
    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;