我需要打开一个文件,对其进行读取锁定,然后尝试获取写入锁定,但如果失败则保持读取锁定。
这在使用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;
}
答案 0 :(得分:0)
为什么不使用pimpl方法?无论如何,你几乎就在那里。创建WinLockFileImpl
和PosixLockFileImpl
,它们都继承了摘要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锁的跟踪。