简体   繁体   English

如何在C++中以独占模式打开文件

[英]How to open file in exclusive mode in C++

I am implementing some file system in C++. Up to now I was using fstream but I realized that it is impossible to open it in exclusive mode.我正在 C++ 中实现一些文件系统。到目前为止,我一直在使用fstream ,但我意识到无法以独占模式打开它。 Since there are many threads I want to allow multiple reads, and when opening file in writing mode I want to open the file in exclusive mode?由于有很多线程我想允许多次读取,并且在以写入模式打开文件时我想以独占模式打开文件?
What is the best way to do it?最好的方法是什么? I think Boost offers some features.我认为Boost提供了一些功能。 And is there any other possibility?还有其他可能吗? I would also like to see simple example.我也想看看简单的例子。 If it is not easy / good to do in C++ I could write in C as well.如果在 C++ 中做起来不容易/不好,我也可以写在 C 中。

I am using Windows.我正在使用 Windows。

On many operating systems, it's simply impossible, so C++ doesn't support it. 在许多操作系统上,这是根本不可能的,因此C ++不支持它。 You'll have to write your own streambuf . 您必须编写自己的streambuf If the only platform you're worried about is Windows, you can possibly use the exclusive mode for opening that it offers. 如果您担心的唯一平台是Windows,则可以使用独占模式打开它提供的功能。 More likely, however, you would want to use some sort of file locking, which is more precise, and is available on most, if not all platforms (but not portably—you'll need LockFileEx under Windows, fcntl under Unix). 但是,您更有可能希望使用某种类型的文件锁定,这种锁定更精确,并且在大多数(如果不是全部)平台上都可用(但不是可移植的-在Windows下需要LockFileEx ,在Unix下需要fcntl )。

Under Posix, you could also use pthread_rwlock . 在Posix下,您还可以使用pthread_rwlock Butenhof gives an implementation of this using classical mutex and condition variables, which are present in C++11, so you could actually implement a portable version (provided all of the readers and writers are in the same process—the Posix requests will work across process boundaries, but this is not true for the C++ threading primitives). Butenhof使用C ++ 11中存在的经典互斥量和条件变量对此进行了实现,因此您实际上可以实现一个可移植的版本(前提是所有的读写器都在同一过程中-Posix请求将在进程边界,但是对于C ++线程原语则不是这样)。

if your app only works on Windows, the win32 API function " CreateFile() " is your choice. 如果您的应用仅在Windows上运行,则选择win32 API函数“ CreateFile() ”。

For example: HANDLE hFile = ::CreateFileW(lpszFileFullPathName, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL); 例如:HANDLE hFile = :: CreateFileW(lpszFileFullPathName,GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,NULL,NULL);

If you are open to using boost, then I would suggest you use the file_lock class. 如果您愿意使用boost,那么我建议您使用file_lock类。 This means you want to keep the filename of the files you open/close because fstream does not do so for you. 这意味着您要保留打开/关闭文件的文件名,因为fstream不会这样做。

They have two modes lock() that you can use for writing (ie only one such lock at a time, the sharable lock prevents this lock too) and lock_sharable() that you can use for reading (ie any number of threads can obtain such a lock). 他们有两个模式lock()您可以用写(在时间,即只有一个这样的锁,共享锁防止这种锁太)和lock_sharable()您可以使用阅读(即任意数量的线程能够取得这样的锁)。

Note that you will find it eventually complicated to manage both, read and write, in this way. 请注意,您将发现以这种方式同时管理读取和写入最终变得很复杂。 That is, if there is always someone to read, the sharable lock may never get released. 也就是说,如果总有人读,则可共享锁可能永远不会释放。 In that case, the exclusive lock will never be given a chance to take.... 在这种情况下,独占锁将永远不会被抓住。

// add the lock in your class
#include <boost/interprocess/sync/file_lock.hpp>
class my_files
{
...
private:
    ...
    boost::file_lock     m_lock;
};

Now when you want to access a file, you can lock it one way or the other. 现在,当您要访问文件时,可以用一种或另一种方式锁定它。 If the thread is in charge of when they do that, you could add functions for the user to have access to the lock. 如果线程负责执行此操作,则可以添加功能供用户访问锁。 If your implementation of the read and write functions in my_files are in charge, you want to get a stack based object that locks and unlocks for you (RAII): 如果负责my_files中的读写功能的实现,则希望获得一个基于堆栈的对象,该对象可以为您锁定和解锁(RAII):

class safe_exclusive_lock
{
public:
    safe_exclusive_lock(file_lock & lock)
        : m_lock_ref(lock)
    {
        m_lock_ref.lock();
    }
    ~safe_exclusive_lock()
    {
        m_lock_ref.unlock();
    }
private:
    file_lock & m_lock_ref;
};

Now you can safely lock the file (ie you lock, do things that may throw, you always unlock before exiting your current {}-block): 现在,您可以安全地锁定文件了(即,锁定,执行可能引发的事情,总是在退出当前的{}块之前解锁):

ssize_t my_files::read(char *buf, size_t len)
{
    safe_exclusive_lock guard(m_lock);
    ...your read code here...
    return len;
} // <- here we get the unlock()

ssize_t my_files::write(char const *buf, size_t len)
{
    safe_exclusive_lock guard(m_lock);
    ...your write code here...
    return len;
} // <- here we get the unlock()

The file_lock uses a file, so you will want to have the fstream file already created whenever the file_lock is created. file_lock使用一个文件,因此无论何时创建file_lock,您都希望已经创建了fstream文件。 If the fstream file may not be created in your constructor, you probably will want to transform the m_lock variable in a unique pointer: 如果可能未在构造函数中创建fstream文件,则可能要在唯一的指针中转换m_lock变量:

private:
    std::unique_ptr<file_lock>  m_lock;

And when you reference it, you now need an asterisk: 当您引用它时,现在需要一个星号:

safe_exclusive_lock guard(*m_lock);

Note that for safety, you should check whether the pointer is indeed allocated, if not defined, it means the file is not open yet so I would suggest you throw: 请注意,为了安全起见,应检查是否确实分配了指针,如果未定义,则意味着文件尚未打开,因此建议您抛出:

if(m_lock)
{
    safe_exclusive_lock guard(*m_lock);
    ...do work here...
}
else
{
    throw file_not_open();
}
// here the lock was released so you cannot touch the file anymore

In the open, you create the lock: 在打开处,创建锁:

bool open(std::string const & filename)
{
    m_stream.open(...);
    ...make sure it worked...
    m_lock.reset(new file_lock(filename));
    // TODO: you may want a try/catch around the m_lock and
    //       close the m_stream if it fails or use a local
    //       variable and swap() on success...
    return true;
}

And do not forget to release the lock object in your close: 并且不要忘记在关闭时释放锁对象:

void close()
{
    m_lock.reset();
}

Well you can manually prevent yourself from opening a file if it has been opened in write mode already. 好吧,如果您已经以写模式打开了文件,则可以手动阻止自己打开文件。 Just keep track internally of which files you've opened in write mode. 只需在内部跟踪您在写入模式下打开了哪些文件即可。

Perhaps you could hash the filename and store it in a table upon open with write access. 也许您可以对文件名进行哈希处理,并在打开时通过写访问权将其存储在表中。 This would allow fast lookup to see if a file has been opened or not. 这将允许快速查找以查看文件是否已打开。

You could rename the file, update it under the new name, and rename it back. 您可以重命名文件,使用新名称对其进行更新,然后重命名。 I've done it, but it's a little heavy. 我已经做到了,但是有点沉重。

Since C++17 there are two options:由于 C++17 有两个选项:

  • In C++23 by using the openmode std::ios::noreplace .在 C++23 中,使用openmode std::ios::noreplace
  • In C++17 by using the std::fopen mode x (exclusive).在 C++17 中通过使用std::fopen模式x (独占)。
    Note: The x mode was added to in C11.注意: x模式在C11中添加到

C++23 and later: C++23 及更高版本:

#include <cerrno>
#include <cstring>
#include <fstream>
#include <iostream>

int main() {
    std::ofstream ofs("the_file", std::ios::noreplace);

    if (ofs) {
        std::cout << "success\n";
    } else {
        std::cerr << "Error: " << std::strerror(errno) << '\n';
    }
}

Demo演示


C++17 and later: C++17 及以后:

#include <cerrno>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iostream>
#include <memory>

struct FILE_closer {
    void operator()(std::FILE* fp) const { std::fclose(fp); }
};

// you may want overloads for `std::filesystem::path`, `std::string` etc too:
std::ofstream open_exclusively(const char* filename) {
    bool excl = [filename] {
        std::unique_ptr<std::FILE, FILE_closer> fp(std::fopen(filename, "wx"));
        return !!fp;
    }();
    auto saveerr = errno;

    std::ofstream stream;
    
    if (excl) {
        stream.open(filename);
    } else {
        stream.setstate(std::ios::failbit);
        errno = saveerr;
    } 
    return stream;
}
int main() {
    std::ofstream ofs = open_exclusively("the_file");

    if (ofs) {
        std::cout << "success\n";
    } else {
        std::cout << "Error: " << std::strerror(errno) << '\n';
    }
}

Demo演示

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM