【问题标题】:proper way to use lock file(s) as locks between multiple processes使用锁文件作为多个进程之间的锁的正确方法
【发布时间】:2011-06-03 15:10:21
【问题描述】:

我有一个情况,其中 2 个不同的进程(我的 C++,其他由 JAVA 中的其他人完成)是某个共享数据文件的写入器和读取器。所以我试图通过编写这样的类来避免竞争条件(编辑:此代码已损坏,这只是一个示例)

class ReadStatus
{
    bool canRead;
public:
    ReadStatus()
    {
        if (filesystem::exists(noReadFileName))
        {
            canRead = false;
            return;
        }
        ofstream noWriteFile;
        noWriteFile.open (noWriteFileName.c_str());
        if ( ! noWriteFile.is_open())
        {
            canRead = false;
            return;
        }
        boost::this_thread::sleep(boost::posix_time::seconds(1));
        if (filesystem::exists(noReadFileName))
        {
            filesystem::remove(noWriteFileName);
            canRead= false;
            return;
        }
        canRead= true;
    }
    ~ReadStatus()
    {
        if (filesystem::exists(noWriteFileName))
            filesystem::remove(noWriteFileName);
    }
    inline bool OKToRead()
    {
        return canRead;
    }
};

用法:

ReadStatus readStatus; //RAII FTW
    if ( ! readStatus.OKToRead())
        return;

这是针对一个程序的,其他的将有类似的类。 想法是: 1.检查其他程序是否创建了他的“我是所有者文件”,如果它有中断,则转到2。 2.创建我的“我是所有者”文件,再次检查其他程序是否创建了他自己的文件,如果它删除了我的文件并中断,则转到3。 3. 做我的阅读,然后删除我的“我是所有者文件”。

请注意,当他们不读或不写时很少发生是可以的,但问题是我仍然看到竞争条件的可能性很小,因为理论上其他程序可以检查我的锁定文件是否存在,看到没有一,然后我创建我的,其他程序创建他自己的,但是在 FS 创建他的文件之前,我再次检查,它不存在,然后灾难发生。这就是我添加一秒延迟的原因,但作为一个 CS 书呆子,我发现运行这样的代码令人不安。 Ofc 我不希望这里有人给我写一个解决方案,但如果有人知道我可以使用的可靠代码的链接,我会很高兴。 附言它必须是文件,因为我不是在编写整个项目,这就是它的安排方式。

P.P.S.: 访问数据文件不是 reader,writer,reader,writer.... 可以是 reader,reader,writer,writer,writer,reader,writer....

P.P.S:其他进程不是用 C++ 编写的 :(,所以 boost 是不可能的。

【问题讨论】:

  • 为什么要重新发明轮子而不是封装操作系统提供的锁定原语?
  • @aix - 就像我说我不是在编写整个项目,也不是在设计它。 Ofc 如果没有人用“FS mutex”魔法回答,我会哭,然后要求更改设计,或者希望超时提供足够好的可能性来避免竞争条件。

标签: c++ filesystems race-condition


【解决方案1】:

在 Unices 上,执行纯文件系统锁定的传统方法是使用带有 mkdir()rmdir() 的专用锁定文件,可以通过单个系统调用原子地创建和删除。您可以通过从不明确测试锁的存在来避免竞争——相反,您总是尝试获取锁。所以:

lock:
    while mkdir(lockfile) fails
        sleep

unlock:
    rmdir(lockfile)

我相信这甚至可以在 NFS 上工作(这通常很糟糕)。

但是,您可能还想研究正确的文件锁定,这样加载会更好;我在 Linux 上为此使用F_SETLK/F_UNLCK fcntl locks(请注意,尽管结构名称不同,但它们与羊群锁不同)。这允许您正确阻塞,直到锁被释放。如果应用程序死了,这些锁也会自动释放,这通常是一件好事。另外,这些将使您可以直接锁定共享文件,而无需拥有单独的锁定文件。这也适用于 NFS。

Windows 有非常相似的文件锁定功能,而且它也有易于使用的全局命名信号量,非常方便进程之间的同步。

【讨论】:

  • 嘿,甚至不测试文件是否存在的好主意。
  • 好主意,使用 mkdir。你知道 boost::filesystem::create_directory() 是否也是原子的吗?如果不是在所有平台上,那么可能只在 Linux 上。
  • 我完全不知道 boost 在 create_directory() 内部做了什么,但我无法想象一个理智的操作系统,其中底层的 make-directory 系统调用 没有 i> 原子的。
  • 是否有任何 Windows 替代 F_SETLK/F_UNLCK fcntl 锁可以在 NFS 上工作并在程序终止时销毁锁?
  • 我上一次在 Windows 上使用 NFS 是很多很多很多很多年前的事了。不知道。
【解决方案2】:

据我所知,您不能可靠地将文件用作多个进程的锁。问题是,当您在一个线程中创建文件时,您可能会遇到中断并且操作系统会切换到另一个进程,因为 I/O 需要很长时间。删除锁定文件也是如此。

如果可以,请查看同步机制部分下的Boost.Interprocess

【讨论】:

  • 对不起,但其他进程不是用 c++ 编写的,JAVA :(。我会更新原始问题
  • 这是不正确的(无论如何对于 Windows)。您可以可靠地将文件用作锁,您只需要知道自己在做什么。阅读机会锁。 msdn.microsoft.com/en-us/library/windows/desktop/…
【解决方案3】:

虽然我通常反对进行可以从构造函数/析构函数抛出的 API 调用(请参阅 boost::filesystem::remove 上的文档),或者通常在没有 catch 块的情况下进行抛出调用,但这并不是您真正要问的。

如果它适用于 Windows,您可以查看 Overlapped IO 库。否则您是否考虑过使用shared memory between the processes 代替?

编辑:刚刚看到另一个进程是 Java。您仍然可以创建一个可以在进程之间共享的命名互斥锁,并使用它来创建围绕文件 IO 位的锁,因此它们必须轮流写入。抱歉,我不懂 Java,所以不知道这是否比共享内存更可行。

【讨论】:

  • Tnx 用于删除信息,但用于其他提升:我的错误是没有指定我不能使用提升,因为它是用于与 JAVA 进程的进程间通信。我现在已经更新了原始问题。它也适用于 Linux,但我更喜欢平台无关的解决方案。
  • (Offtopic )还有关于缺少 catch 块的问题:你觉得拥有它有什么意义吗?我的意思是如果删除失败并且它是一种锁定机制,我看不到任何合理的方法可以从中恢复。我应该像疯子一样循环直到没有异常,还是有更好的方法来处理它?
  • 理想情况下,如果您想要一个删除请求队列或其他更高级的东西,并在失败时推动另一次删除尝试,那么您至少会记录错误或通知用户,这取决于您,但有通常一个可行的原因被删除失败(就像其他一些写入它的进程一样)
  • 还可以阅读这篇文章以了解何时、何时不抛出以及如何处理异常(包括构造函数和析构函数):parashift.com/c++-faq-lite/exceptions.html
  • 我喜欢随意的信息展示。 Herb Sutter 有一篇更好的文章,但我找不到链接。要点是不要从析构函数中抛出,而是从构造函数中抛出,如果您需要发出创建失败的信号,并确保处理它。
猜你喜欢
  • 1970-01-01
  • 2019-11-22
  • 2014-12-03
  • 1970-01-01
  • 2017-07-26
  • 1970-01-01
  • 1970-01-01
  • 2013-03-12
  • 2017-12-14
相关资源
最近更新 更多