LockFileEx读/写升级/降级

时间:2010-07-02 19:15:52

标签: c++ winapi locking

我需要打开一个文件,对其进行读取锁定,然后尝试获取写入锁定,但如果失败则保持读取锁定。

这在使用fcntl锁定的POSIX中非常有效。

在Windows中,我可以使用LockFileEx来获取文件锁。我可以同时获得读锁和写锁(共享和独占)。

但是,似乎在Windows中我必须首先使用独占写锁 ,然后添加读锁。这与我在POSIX上所做的相反,它会导致我的抽象层出现问题。当我在POSIX中按顺序执行此操作时,我通过读取锁定来丢失写入锁定,因为fcntl替换现有锁定而不是像Windows那样添加锁定。

我可以用#ifdefs破解它来改变呼叫站点的锁定顺序,但我正在寻找好的想法来修复我的抽象代码。

// This is the header file
struct LockFileImpl;
class LockFile {
    protected:
    boost::scoped_ptr<LockFileImpl> p;

    public:
    LockFile(const File &); 
    virtual ~LockFile();

    void unlock() const;
    void rd_lock() const;
    void wr_lock() const;
    bool rd_try() const;
    bool wr_try() const;
};

class LockFileRead : public LockFile{
    public:
    LockFileRead(const File &f) : LockFile(f)
    { rd_lock(); }
};

class LockFileWrite : public LockFile{
    public:
    LockFileWrite(const File &f) : LockFile(f)
    { wr_lock(); }
};

// This is the Win32 implementation file. There's a different one for POSIX.
struct LockFileImpl
{
    handle_t hFile;
    bool rd_locked;
    bool wr_locked;

    LockFileImpl(handle_t x) : hFile(x), rd_locked(false), wr_locked(false)
    {}
};

LockFile::LockFile(const File &f)
    : p( new LockFileImpl(f.handle()) )
{
}

LockFile::~LockFile()
{
    unlock();
}


void LockFile::unlock() const
{
    if(p->wr_locked) {
        throw_win32_err_if( UnlockFile(p->hFile, 0, 0, 1, 0) == 0 );
        p->wr_locked = false;
    }
    if(p->rd_locked) {
        throw_win32_err_if( UnlockFile(p->hFile, 0, 0, 1, 0) == 0 );
        p->rd_locked = false;
    }
}

void LockFile::rd_lock() const
{
    OVERLAPPED over = {0};
    over.Offset = 0;
    throw_win32_err_if( !LockFileEx(p->hFile, 0, 0, 1, 0, &over) );
    p->rd_locked = true;
    if(p->wr_locked) {
        throw_win32_err_if( UnlockFile(p->hFile, 0, 0, 1, 0) == 0 );
        p->wr_locked = false;
    }
}

void LockFile::wr_lock() const
{
    OVERLAPPED over = {0};
    over.Offset = 0;
    throw_win32_err_if( !LockFileEx(p->hFile, LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &over) );
    p->wr_locked = true;
}

bool LockFile::rd_try() const
{
    OVERLAPPED over = {0};
    over.Offset = 0;
    bool r = !!LockFileEx(p->hFile, LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &over);
    if(r) {
        p->rd_locked = true;
        if(p->wr_locked) {
            throw_win32_err_if( UnlockFile(p->hFile, 0, 0, 1, 0) == 0 );
            p->wr_locked = false;
        }
    }
    return r;
}

bool LockFile::wr_try() const
{
    OVERLAPPED over = {0};
    over.Offset = 0;
    bool r = !!LockFileEx(p->hFile, LOCKFILE_FAIL_IMMEDIATELY|LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &over);
    if(r) {
        p->wr_locked = true;
    }
    return r;
}

2 个答案:

答案 0 :(得分:0)

为什么不使用pimpl方法?无论如何,你几乎就在那里。创建WinLockFileImplPosixLockFileImpl,它们都继承了摘要LockFileImpl。然后,在以下代码周围放置一个ifdef以确定在编译时使用哪个类。你必须已经有ifdef在其他平台上编译时删除了windows代码,对吗?

LockFile::LockFile(const File &f)
#ifdef POSIX
    : p( new PosixLockFileImpl(f.handle()) )
#else
    : p( new WinLockFileImpl(f.handle()) )
#endif

哦,你需要将代码移到更改LockFile的实现类中,看起来更像这样:

void LockFile::unlock() const
{
    p->unlock();
}

答案 1 :(得分:0)

我们的锁定要求非常有限,但以下代码似乎可以模仿POSIX fcntl以满足我们的需求。注意hack根据锁定的区域大小来区分读锁和写锁(根据你的例子,这个hack可能适合你)。下面的代码假设文件小于4GB。

// fcntl flock definitions
#define F_SETLK  8   // Non-Blocking set or clear a lock
#define F_SETLKW 9   // Blocking set or clear a lock
#define F_RDLCK  1   // read lock
#define F_WRLCK  2   // write lock
#define F_UNLCK  3   // remove lock
struct flock {
    short l_type;   // F_RDLCK, F_WRLCK, or F_UNLCK
    short l_whence; // flag to choose starting offset, must be SEEK_SET
    long  l_start;  // relative offset, in bytes, must be 0
    long  l_len;    // length, in bytes; 0 means lock to EOF, must be 0
    short l_pid;    // unused (returned with the unsupported F_GETLK)
    short l_xxx;    // reserved for future use
};

// only works for (SEEK_SET, start=0, len=0) file locking.
__inline int fcntl(int fd, int cmd, ...)
{
    va_list a;
    va_start(a, cmd);
    switch(cmd)
    {
    case F_SETLK:
        {
            struct flock *l = va_arg(a, struct flock*);
            switch(l->l_type)
            {
            case F_RDLCK:
                {
                    OVERLAPPED o = { 0 };
                    HANDLE h = (HANDLE)_get_osfhandle(fd);
                    if (l->l_whence != SEEK_SET || l->l_start != 0 || l->l_len != 0)
                    {
                        _set_errno(ENOTSUP);
                        return -1;
                    }
                    if (!LockFileEx(h, LOCKFILE_FAIL_IMMEDIATELY, 0, 0, 1, &o)) // read lock
                    {
                        unsigned long x = GetLastError();
                        _set_errno(GetLastError() == ERROR_LOCK_VIOLATION ? EAGAIN : EBADF);
                        return -1;
                    }
                    UnlockFile(h, 0, 0, 1, 1); // write lock
                }
                break;
            case F_WRLCK:
                {
                    OVERLAPPED o = { 0 };
                    HANDLE h = (HANDLE)_get_osfhandle(fd);
                    if (l->l_whence != SEEK_SET || l->l_start != 0 || l->l_len != 0)
                    {
                        _set_errno(ENOTSUP);
                        return -1;
                    }
                    if (!LockFileEx(h, LOCKFILE_FAIL_IMMEDIATELY|LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 1, &o)) // write lock
                    {
                        unsigned long x = GetLastError();
                        _set_errno(GetLastError() == ERROR_LOCK_VIOLATION ? EAGAIN : EBADF);
                        return -1;
                    }
                    UnlockFile(h, 0, 0, 0, 1); // read lock
                }
                break;
            case F_UNLCK:
                {
                    HANDLE h = (HANDLE)_get_osfhandle(fd);
                    if (l->l_whence != SEEK_SET || l->l_start != 0 || l->l_len != 0)
                    {
                        _set_errno(ENOTSUP);
                        return -1;
                    }
                    UnlockFile(h, 0, 0, 0, 1); // read lock
                    UnlockFile(h, 0, 0, 1, 1); // write lock
                }
                break;
            default:
                _set_errno(ENOTSUP);
                return -1;
            }
        }
        break;
    case F_SETLKW:
        {
            struct flock *l = va_arg(a, struct flock*);
            switch(l->l_type)
            {
            case F_RDLCK:
                {
                    OVERLAPPED o = { 0 };
                    HANDLE h = (HANDLE)_get_osfhandle(fd);
                    if (l->l_whence != SEEK_SET || l->l_start != 0 || l->l_len != 0)
                    {
                        _set_errno(ENOTSUP);
                        return -1;
                    }
                    if(!LockFileEx(h, 0, 0, 0, 1, &o)) // read lock
                    {
                        unsigned long x = GetLastError();
                        return -1;
                    }
                    UnlockFile(h, 0, 0, 1, 1); // write lock
                }
                break;
            case F_WRLCK:
                {
                    OVERLAPPED o = { 0 };
                    HANDLE h = (HANDLE)_get_osfhandle(fd);
                    if (l->l_whence != SEEK_SET || l->l_start != 0 || l->l_len != 0)
                    {
                        _set_errno(ENOTSUP);
                        return -1;
                    }
                    if (!LockFileEx(h, LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 1, &o)) // write lock
                    {
                        unsigned long x = GetLastError();
                        return -1;
                    }
                    UnlockFile(h, 0, 0, 0, 1); // read lock
                }
                break;
            case F_UNLCK:
                {
                    flock *l = va_arg(a, flock*);
                    HANDLE h = (HANDLE)_get_osfhandle(fd);
                    if (l->l_whence != SEEK_SET || l->l_start != 0 || l->l_len != 0)
                    {
                        _set_errno(ENOTSUP);
                        return -1;
                    }
                    UnlockFile(h, 0, 0, 0, 1); // read lock
                    UnlockFile(h, 0, 0, 1, 1); // write lock
                }
                break;
            default:
                _set_errno(ENOTSUP);
                return -1;
            }
        }
        break;
    default:
        _set_errno(ENOTSUP);
        return -1;
    }

    return 0;
}

正如您所指出的,fcntl锁定到FileLock锁定的主要问题是(来自文档):

  

如果使用独占锁和共享锁锁定相同范围,则需要两次解锁操作才能解锁该区域;第一次解锁操作解锁独占锁,第二次解锁操作解锁共享锁。

这意味着,如果没有首先完全释放该区域的锁定,您就无法从共享锁定转变为同一区域的独占锁定。您使用标志的方法很接近,我想如果您添加了另一个标记为i_have_both_locks_but_only_really_want_the_exclusive_lock的标记,那么您的解决方案可以正常工作。我们没有编写自己的界面(我们被fcntl困住)的奢侈。但是,幸运的是,使用fcntl的代码只是想锁定整个文件而文件很小。另一种解决方案是在fcntl调用中放置一个std :: map来跟踪fcntl锁与所拥有的FileLock锁的跟踪。