我正在将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 !)
有没有办法在某些时候删除锁定文件而不引入这种竞争条件?
答案 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)
因此,无需比较旧文件路径/新文件路径的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;