群():在没有竞争条件的情况下删除锁定的文件?
Posted
技术标签:
【中文标题】群():在没有竞争条件的情况下删除锁定的文件?【英文标题】:flock(): removing locked file without race condition? 【发布时间】:2013-07-16 13:02:01 【问题描述】:我将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 !)
有没有办法在不引入这种竞争条件的情况下删除锁定文件?
【问题讨论】:
问题是您正在尝试实施细粒度锁定策略(文件代表资源),但您对共享粗粒度资源(文件系统)存在争用。您要么需要在更新细粒度锁之前对锁文件目录进行全局锁,要么重新设计您的锁策略。 您能详细说明您的需求吗?例如,如果程序都锁定相同的概念资源,为什么不让程序使用 1 个众所周知的文件名? 【参考方案1】:如果您仅将这些文件用于锁定,而不实际写入它们,那么我建议您将目录条目本身的存在视为持有锁定的指示,并完全避免使用flock
。
为此,您需要构造一个操作,该操作创建一个目录条目并在它已经存在时报告错误。在 Linux 和 大多数 文件系统上,将 O_EXCL
传递给 open
将适用于此。但是某些平台和某些文件系统(尤其是较旧的 NFS)不支持此功能。因此,man page for open
提出了一个替代方案:
想要使用 lockfile 执行原子文件锁定并且需要避免依赖 NFS 对
O_EXCL
的支持的便携式程序可以在同一文件系统上创建唯一文件(例如,合并主机名和 PID),并使用link
(2) 链接到锁定文件。如果link
(2) 返回0,则锁定成功。否则,在唯一文件上使用stat
(2) 来检查其链接数是否增加到2,这样锁也成功了。
所以这看起来像是一个正式记录的锁定方案,因此表明了一定程度的支持和最佳实践建议。但我也看到了其他方法。 bzr 例如在大多数地方使用目录而不是符号链接。引用its source code:
一个锁在磁盘上由一个特定名称的目录表示, 包含一个信息文件。通过重命名一个锁来完成 临时目录到位。我们使用临时目录是因为 对于所有已知的传输和文件系统,我们相信只有一个 尝试申领锁将成功,而其他尝试将失败。 (文件 不会这样做,因为某些文件系统或传输只有 重命名和覆盖,很难分辨谁赢了。)
上述方法的一个缺点是它们不会阻塞:失败的锁定尝试会导致错误,但不会等到锁定可用。您将不得不轮询锁,这可能会因为锁争用而产生问题。在这种情况下,您可能希望进一步脱离基于文件系统的方法,转而使用第三方实现。但是关于如何做 ipc 互斥的一般问题已经被问到了,所以我建议你search for [ipc] [mutex]
看看结果,特别是this one。顺便说一句,这些标签也可能对您的帖子有用。
【讨论】:
这种方法遇到的问题是,如果一个进程在持有锁时死掉了,那么就没有可靠的方法来自动获取锁。相反,在flock
方法中,如果一个进程在持有锁时死亡,该文件将保留在文件系统上,但不再是flock
'ed;其他进程现在可以获取锁。
@davidg:这是一个有效的观点。一些实现将锁定过程的时间戳和/或 pid 写入链接到锁定文件名的文件,或锁定目录中的知名文件。这样,您可以检查具有该 ID 的进程是否仍然处于活动状态,并且您还可以在给定时间后使锁过期。【参考方案2】:
对不起,如果我回答了一个死问题:
锁定文件后,打开另一个副本,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 之后或在关键部分中,则它的描述符已锁定在文件系统上。
【讨论】:
取消链接关闭后可以解锁文件吗?手册说flock“在fd指定的打开文件上应用或删除咨询锁”。 你不应该在最后关闭fd以防止泄漏吗? 不可移植!例如。在 Windows 上st_ino
始终为 0。
什么是状态机模型?
这个解决方案不起作用,问题是关于3个进程A、B、C。【参考方案3】:
-
在 Unix 中,可以在打开文件时将其删除 - inode 将一直保留,直到所有在其文件描述符列表中包含它的进程结束为止
在 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;
【讨论】:
@Bob 怎么样?这段代码从来没有unlink
s 一个没有排他锁的文件,当检查完成后,没有其他进程可以拥有这样的排他锁,因为它自己拥有锁。以上是关于群():在没有竞争条件的情况下删除锁定的文件?的主要内容,如果未能解决你的问题,请参考以下文章