如何保证应用程序只有一个实例?以前学MFC的时候就遇到过这个问题,当时只要简单的设置下参数就可以了,没有深究,现在研究levelDB又遇到这个问题,所以,我决定打破沙锅问到底,看看到底是怎么实现的!
如果你月薪一万,别人问你工资多少,你告诉别人你月薪一万,那没有什么特别的,谁也不会在意,如果你月薪一万,却告诉别人你月收入五位数,那,给对方的感觉是完全不一样的,尤其是相亲的时候:-)
我一直好奇如何保证一个应用程序只能有一个实例,当我知道了以后,反而有那么一点点失望,因为它就那么回事,真没有什么高深的东西,你不知道,只是因为你不知道,或者说是因为你知道得太少,而不是这东西太难了。废话真多,下面就来看一下如何保证应用程序只能有一个实例的吧。
直接把目光定位到db_impl.cc文件的下DB::Open函数里的这行代码:
Status s = impl->Recover(&edit); // Handles create_if_missing, error_if_exists
Recover函数完成levelDB 启动时的一些基本检查,主要包括下面两部分:
下面是Recover函数的部分代码:
// Ignore error from CreateDir since the creation of the DB is // committed only when the descriptor is created, and this directory // may already exist from a previous failed creation attempt. env_->CreateDir(dbname_); assert(db_lock_ == NULL); Status s = env_->LockFile(LockFileName(dbname_), &db_lock_); if (!s.ok()) { return s; } if (!env_->FileExists(CurrentFileName(dbname_))) { if (options_.create_if_missing) { s = NewDB(); if (!s.ok()) { return s; } } else { return Status::InvalidArgument( dbname_, "does not exist (create_if_missing is false)"); } } else { if (options_.error_if_exists) { return Status::InvalidArgument( dbname_, "exists (error_if_exists is true)"); } }
可以看到,它就做了我们上面说的两件事。我们主要来看看LockFile函数。在看LockFile函数前,先把LockFileName 弄清楚了,它的定义如下:
std::string LockFileName(const std::string& dbname) { return dbname + "/LOCK";}
假如我们的数据库名子为testdb ,那么,这个函数的功能就是返回一个testdb/LOCK字符串,用于我们定义testdb 目录下的LOCK文件,LOCK文件是一个空文件,它的存在就是帮助实现一个应用程序只存在一个实例。
LockFile 是env_
的成员函数,我们知道,env_
是一个指向PosixEnv的指针,所以,我们直接把注意力转移到PosixEnv 类的LockFile 文件即可。
virtual Status LockFile(const std::string& fname, FileLock** lock) { *lock = NULL; Status result; int fd = open(fname.c_str(), O_RDWR | O_CREAT, 0644); if (fd < 0) { result = IOError(fname, errno); } else if (!locks_.Insert(fname)) { close(fd); result = Status::IOError("lock " + fname, "already held by process"); } else if (LockOrUnlock(fd, true) == -1) { result = IOError("lock " + fname, errno); close(fd); locks_.Remove(fname); } else { PosixFileLock* my_lock = new PosixFileLock; my_lock->fd_ = fd; my_lock->name_ = fname; *lock = my_lock; } return result; }
LockFile首先打开文件,如果文件不存在,就创建,打开文件以后判断文件是否打开失败,如果打开失败,就返回错误信息,否则,就执行其他语句,我们把注意力集中到下面这条语句。
} else if (LockOrUnlock(fd, true) == -1) {
顾名思意,它要锁住当前文件,怎么做到的呢?我们看看这个函数的定义就知道了。
static int LockOrUnlock(int fd, bool lock) { errno = 0; struct flock f; memset(&f, 0, sizeof(f)); f.l_type = (lock ? F_WRLCK : F_UNLCK); f.l_whence = SEEK_SET; f.l_start = 0; f.l_len = 0; // Lock/unlock entire file return fcntl(fd, F_SETLK, &f);}
如果你看过《Unix环境高级编程》,你一定跟我一样,看到这里就笑了,然后心里默念:原来如此!!
我们只看关键的语句,从函数的名字就可以看出来,这个函数要实现对文件的加锁解锁功能,当文件已经被加锁了,我们就解锁,当文件没有锁的时候,我们就加锁。
f.l_type = (lock ? F_WRLCK : F_UNLCK);
然后通过fcntl 系统调用来对文件加锁,当我们对文件加了F_WRLCK(独占锁)锁以后,其他程序打开这个文件的时候,就会出错。
当我们有一个levelDB实例运行的时候,第二个levelDB实例在调用LockFile函数企图打开LOCK文件时,由于第一个levelDB 实例已经将该文件加锁了,第二个levelDB实例打开LOCK文件时就会出错,出错以后就直接返回了。这样就实现了应用程序只有一个实例在运行。
int fd = open(fname.c_str(), O_RDWR | O_CREAT, 0644);
有加锁操作,自然也有解锁操作了,在~DBImpl()函数中有这么一条语句:
if (db_lock_ != NULL) { env_->UnlockFile(db_lock_); }
对于以前不知道如何保证应用程序只能有一个实例的你,是不是有点惊讶,怎么这么简单,都是以前学过的知识!
联系客服