代码之家  ›  专栏  ›  技术社区  ›  Zan Lynx

LockFileEx读/写升级/降级

  •  4
  • Zan Lynx  · 技术社区  · 14 年前

    我需要打开一个文件,读取并锁定它,然后尝试获取一个写锁,但如果失败,则保留读锁。

    这在使用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 回复  |  直到 14 年前
        1
  •  0
  •   Harvey    14 年前

    为什么不使用pimpl方法呢?不管怎样,你快到了。创建 WinLockFileImpl PosixLockFileImpl 两者都继承了一个抽象的 LockFileImpl . 然后,放一个 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();
    }
    
        2
  •  0
  •   mheyman    11 年前

    我们有非常有限的锁定需求,但下面的代码似乎足以模拟POSIX fcntl。注意根据锁定区域大小区分读锁和写锁的技巧(根据您的示例,此技巧可能对您有用)。下面的代码假设文件小于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锁定的主要问题是,正如您所注意到的,有一点需要注意(来自文档):

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

    这意味着,如果不首先完全释放同一区域上的锁,就不能从一个共享锁变成同一区域的唯一锁。你使用标志的方法很接近了,我想如果你添加了另一个标志,上面写着我有两个锁,但你只想拥有唯一的锁,那么你的解决方案就可以工作了。我们没有足够的时间编写自己的界面(我们被fcntl困住了)。但是,幸运的是,使用fcntl的代码只想锁定整个文件,而且文件很小。另一种解决方案是在fcntl调用中放置std::map来跟踪fcntl锁和所拥有的FileLock锁。